mirror of
https://codeberg.org/MarkusThielker/next-ory.git
synced 2025-04-18 08:31:18 +00:00
NORY-36: add ory keto to application stack (#38)
This introduces the protection of the admin dashboard using Ory Keto. A script for applying the `admin` role to an identity has been added.
This commit is contained in:
commit
b6ce48e03e
23 changed files with 356 additions and 72 deletions
25
README.md
25
README.md
|
@ -1,15 +1,14 @@
|
||||||
# Next-Ory
|
# Next-Ory
|
||||||
|
|
||||||
Get started with ORY authentication quickly and easily.
|
Get started with the Ory stack quickly and easily.
|
||||||
|
|
||||||
> [!Warning]
|
> [!Warning]
|
||||||
> This project is work in progress. There is no guarantee that everything will work as it should and breaking changes in
|
> This project is work in progress. There is no guarantee that everything will work as it should and breaking changes in
|
||||||
> the future are possible.
|
> the future are possible.
|
||||||
|
|
||||||
The goal of this project is to create an easy-to-use setup to self-host [Ory Kratos](https://www.ory.sh/kratos)
|
The goal of this project is to create an easy-to-use setup to self-host the [Ory](https://www.ory.sh) stack with all its
|
||||||
and [Ory Hydra](https://www.ory.sh/hydra). It will contain an authentication UI, implementing all self-service flows for
|
components. It will contain an authentication UI, implementing all self-service flows for Ory Kratos and Ory Hydra, as
|
||||||
Ory Kratos and Ory Hydra, as well as an admin UI. All UI components are written in NextJS and Typescript, and styled
|
well as an admin UI. All UI components are written in NextJS and Typescript, and styled using shadcn/ui and TailwindCSS.
|
||||||
using shadcn/ui and TailwindCSS.
|
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
|
@ -41,6 +40,15 @@ bun install
|
||||||
bun run dev
|
bun run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To access the admin dashboard, the `identity` has to be a `member` of the `admin` role. (Relation: roles:admin@<
|
||||||
|
identity_id>#member) <br/>
|
||||||
|
The identity ID is displayed on the screen when accessing the dashboard without sufficient permissions. <br/>
|
||||||
|
Use the identity ID to execute the following script with the identity ID as an argument.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sh docker/ory-dev/keto-make-admin.sh <identity_id>
|
||||||
|
```
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
*soon.*
|
*soon.*
|
||||||
|
@ -58,11 +66,10 @@ Hydra. It is implemented in a way, that customizing style and page layout is ver
|
||||||
|
|
||||||
## Admin Dashboard
|
## Admin Dashboard
|
||||||
|
|
||||||
Right now I am working on the admin dashboard for Ory Kratos. It will provide you with an overview of your instance and
|
Right now I am working on the admin dashboard for all Ory applications. It will provide you with an overview of your
|
||||||
let you manage users, OAuth2 applications and more. It is ***work in progress*** and should not be used in anything
|
instances and let you manage users, OAuth2 applications and more. It is ***work in progress*** and should be handled
|
||||||
important as it is not yet protected by Keto permissions but only by a valid Kratos session!
|
with caution.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Next-Ory - Authentication
|
# Next-Ory - Authentication
|
||||||
|
|
||||||
This directory contains a NextJS 14 (app router) UI Node, implementing all Ory Kratos and Ory Hydra UI flows.
|
This directory contains a NextJS 15 (app router) UI Node, implementing all Ory Kratos and Ory Hydra UI flows.
|
||||||
|
|
||||||
## Stack
|
## Stack
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ const inter = Inter({ subsets: ['latin'] });
|
||||||
const APP_NAME = 'Next Ory';
|
const APP_NAME = 'Next Ory';
|
||||||
const APP_DEFAULT_TITLE = 'Next Ory';
|
const APP_DEFAULT_TITLE = 'Next Ory';
|
||||||
const APP_TITLE_TEMPLATE = `%s | ${APP_DEFAULT_TITLE}`;
|
const APP_TITLE_TEMPLATE = `%s | ${APP_DEFAULT_TITLE}`;
|
||||||
const APP_DESCRIPTION = 'Get started with ORY authentication quickly and easily.';
|
const APP_DESCRIPTION = 'Get started with Ory authentication quickly and easily.';
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
applicationName: APP_NAME,
|
applicationName: APP_NAME,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Next-Ory - Dashboard
|
# Next-Ory - Dashboard
|
||||||
|
|
||||||
This directory contains a NextJS 15 (app router) UI Node, implementing the admin dashboard to the ORY Kratos instance.
|
This directory contains a NextJS 15 (app router) UI Node, implementing the admin dashboard for the Ory admin APIs.
|
||||||
|
|
||||||
## Stack
|
## Stack
|
||||||
|
|
||||||
|
|
37
dashboard/src/app/(inside)/layout.tsx
Normal file
37
dashboard/src/app/(inside)/layout.tsx
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import '../globals.css';
|
||||||
|
import { Toaster } from '@/components/ui/sonner';
|
||||||
|
import React from 'react';
|
||||||
|
import { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar';
|
||||||
|
import { AppSidebar } from '@/components/app-sidebar';
|
||||||
|
import { Separator } from '@/components/ui/separator';
|
||||||
|
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList } from '@/components/ui/breadcrumb';
|
||||||
|
|
||||||
|
export default function InsideLayout({ children }: Readonly<{ children: React.ReactNode }>) {
|
||||||
|
return (
|
||||||
|
<SidebarProvider className="max-h-screen min-h-screen">
|
||||||
|
<AppSidebar className="mx-1"/>
|
||||||
|
<SidebarInset className="overflow-hidden p-6 space-y-6">
|
||||||
|
<header className="flex h-4 items-center gap-2">
|
||||||
|
<SidebarTrigger className="-ml-1 p-1"/>
|
||||||
|
<Separator orientation="vertical" className="mr-2 h-4"/>
|
||||||
|
{
|
||||||
|
// TODO: implement dynamic Breadcrumbs
|
||||||
|
}
|
||||||
|
<Breadcrumb>
|
||||||
|
<BreadcrumbList>
|
||||||
|
<BreadcrumbItem className="hidden md:block">
|
||||||
|
<BreadcrumbLink href="/">
|
||||||
|
Ory Dashboard
|
||||||
|
</BreadcrumbLink>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
</BreadcrumbList>
|
||||||
|
</Breadcrumb>
|
||||||
|
</header>
|
||||||
|
<div className="flex-1 overflow-scroll">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</SidebarInset>
|
||||||
|
<Toaster/>
|
||||||
|
</SidebarProvider>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { getHydraMetadataApi, getKratosMetadataApi } from '@/ory/sdk/server';
|
import { getHydraMetadataApi, getKetoMetadataApi, getKratosMetadataApi } from '@/ory/sdk/server';
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ export default async function RootPage() {
|
||||||
const kratosDBStatusData = await fetch(process.env.ORY_KRATOS_ADMIN_URL + '/health/ready');
|
const kratosDBStatusData = await fetch(process.env.ORY_KRATOS_ADMIN_URL + '/health/ready');
|
||||||
const kratosDBStatus = await kratosDBStatusData.json() as { status: string };
|
const kratosDBStatus = await kratosDBStatusData.json() as { status: string };
|
||||||
|
|
||||||
|
|
||||||
const hydraMetadataApi = await getHydraMetadataApi();
|
const hydraMetadataApi = await getHydraMetadataApi();
|
||||||
|
|
||||||
const hydraVersion = await hydraMetadataApi
|
const hydraVersion = await hydraMetadataApi
|
||||||
|
@ -30,13 +31,27 @@ export default async function RootPage() {
|
||||||
const hydraDBStatusData = await fetch(process.env.ORY_KRATOS_ADMIN_URL + '/health/ready');
|
const hydraDBStatusData = await fetch(process.env.ORY_KRATOS_ADMIN_URL + '/health/ready');
|
||||||
const hydraDBStatus = await hydraDBStatusData.json() as { status: string };
|
const hydraDBStatus = await hydraDBStatusData.json() as { status: string };
|
||||||
|
|
||||||
|
|
||||||
|
const ketoMetadataApi = await getKetoMetadataApi();
|
||||||
|
|
||||||
|
const ketoVersion = await ketoMetadataApi
|
||||||
|
.getVersion()
|
||||||
|
.then(res => res.data.version)
|
||||||
|
.catch(() => '');
|
||||||
|
|
||||||
|
const ketoStatusData = await fetch(process.env.ORY_KETO_ADMIN_URL + '/health/alive');
|
||||||
|
const ketoStatus = await ketoStatusData.json() as { status: string };
|
||||||
|
|
||||||
|
const ketoDBStatusData = await fetch(process.env.ORY_KETO_ADMIN_URL + '/health/ready');
|
||||||
|
const ketoDBStatus = await ketoDBStatusData.json() as { status: string };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-3xl font-bold leading-tight tracking-tight">Software Stack</p>
|
<p className="text-3xl font-bold leading-tight tracking-tight">Software Stack</p>
|
||||||
<p className="text-lg font-light">See the list of all applications in your stack</p>
|
<p className="text-lg font-light">See the list of all applications in your stack</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-row space-x-4">
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||||
<Card className="flex-1">
|
<Card className="flex-1">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>
|
<CardTitle>
|
||||||
|
@ -73,7 +88,24 @@ export default async function RootPage() {
|
||||||
</Badge>
|
</Badge>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<div className="flex-1"></div>
|
<Card className="flex-1">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>
|
||||||
|
Ory Keto
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Version {ketoVersion}
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-x-1">
|
||||||
|
<Badge variant={kratosStatus.status === 'ok' ? 'success' : 'destructive'}>
|
||||||
|
Keto {ketoStatus.status.toUpperCase()}
|
||||||
|
</Badge>
|
||||||
|
<Badge variant={kratosStatus.status === 'ok' ? 'success' : 'destructive'}>
|
||||||
|
Database {ketoDBStatus.status.toUpperCase()}
|
||||||
|
</Badge>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
<div className="flex-1"></div>
|
<div className="flex-1"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -6,7 +6,7 @@ import { DataTable } from '@/components/ui/data-table';
|
||||||
import { CircleCheck, CircleX, Copy, MoreHorizontal, Trash, UserCheck, UserMinus, UserPen, UserX } from 'lucide-react';
|
import { CircleCheck, CircleX, Copy, MoreHorizontal, Trash, UserCheck, UserMinus, UserPen, UserX } from 'lucide-react';
|
||||||
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card';
|
import { HoverCard, HoverCardContent, HoverCardTrigger } from '@/components/ui/hover-card';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import { FetchIdentityPageProps } from '@/app/user/page';
|
import { FetchIdentityPageProps } from '@/app/(inside)/user/page';
|
||||||
import { Spinner } from '@/components/ui/spinner';
|
import { Spinner } from '@/components/ui/spinner';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
@ -29,7 +29,7 @@ import {
|
||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from '@/components/ui/alert-dialog';
|
} from '@/components/ui/alert-dialog';
|
||||||
import { blockIdentity, deleteIdentity, deleteIdentitySessions, unblockIdentity } from '@/app/user/action';
|
import { blockIdentity, deleteIdentity, deleteIdentitySessions, unblockIdentity } from '@/app/(inside)/user/action';
|
||||||
|
|
||||||
interface IdentityDataTableProps {
|
interface IdentityDataTableProps {
|
||||||
data: Identity[];
|
data: Identity[];
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IdentityDataTable } from '@/app/user/data-table';
|
import { IdentityDataTable } from '@/app/(inside)/user/data-table';
|
||||||
import { getIdentityApi } from '@/ory/sdk/server';
|
import { getIdentityApi } from '@/ory/sdk/server';
|
||||||
import { SearchInput } from '@/components/search-input';
|
import { SearchInput } from '@/components/search-input';
|
||||||
|
|
39
dashboard/src/app/(outside)/unauthorised/page.tsx
Normal file
39
dashboard/src/app/(outside)/unauthorised/page.tsx
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { ErrorDisplay } from '@/components/error';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { kratos, LogoutLink } from '@/ory';
|
||||||
|
import { LogOut } from 'lucide-react';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { Session } from '@ory/client';
|
||||||
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
|
||||||
|
export default function UnauthorizedPage() {
|
||||||
|
|
||||||
|
const [session, setSession] = useState<Session | undefined>(undefined);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
kratos.toSession()
|
||||||
|
.then((response) => setSession(response.data));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bg-background min-h-screen p-16">
|
||||||
|
<ErrorDisplay
|
||||||
|
title="Unauthorised"
|
||||||
|
message="You are unauthorised to access this application!"/>
|
||||||
|
|
||||||
|
{
|
||||||
|
session ?
|
||||||
|
<p className="text-xs text-neutral-500">USER ID {session.identity?.id}</p>
|
||||||
|
:
|
||||||
|
<Skeleton className="w-72 h-4"/>
|
||||||
|
}
|
||||||
|
|
||||||
|
<Button className="mt-8 space-x-2" onClick={LogoutLink()}>
|
||||||
|
<LogOut className="h-4 w-4"/>
|
||||||
|
<span>Logout</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -2,20 +2,15 @@ import type { Viewport } from 'next';
|
||||||
import { Inter } from 'next/font/google';
|
import { Inter } from 'next/font/google';
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { Toaster } from '@/components/ui/sonner';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ThemeProvider } from 'next-themes';
|
import { ThemeProvider } from 'next-themes';
|
||||||
import { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar';
|
|
||||||
import { AppSidebar } from '@/components/app-sidebar';
|
|
||||||
import { Separator } from '@/components/ui/separator';
|
|
||||||
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList } from '@/components/ui/breadcrumb';
|
|
||||||
|
|
||||||
const inter = Inter({ subsets: ['latin'] });
|
const inter = Inter({ subsets: ['latin'] });
|
||||||
|
|
||||||
const APP_NAME = 'Next Ory';
|
const APP_NAME = 'Next Ory';
|
||||||
const APP_DEFAULT_TITLE = 'Next Ory';
|
const APP_DEFAULT_TITLE = 'Next Ory';
|
||||||
const APP_TITLE_TEMPLATE = `%s | ${APP_DEFAULT_TITLE}`;
|
const APP_TITLE_TEMPLATE = `%s | ${APP_DEFAULT_TITLE}`;
|
||||||
const APP_DESCRIPTION = 'Get started with ORY authentication quickly and easily.';
|
const APP_DESCRIPTION = 'Get started with Ory authentication quickly and easily.';
|
||||||
|
|
||||||
export const metadata = {
|
export const metadata = {
|
||||||
applicationName: APP_NAME,
|
applicationName: APP_NAME,
|
||||||
|
@ -56,31 +51,7 @@ export default function RootLayout({ children }: Readonly<{ children: React.Reac
|
||||||
enableSystem
|
enableSystem
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
<SidebarProvider className="max-h-screen min-h-screen">
|
{children}
|
||||||
<AppSidebar className="mx-1"/>
|
|
||||||
<SidebarInset className="overflow-hidden p-6 space-y-6">
|
|
||||||
<header className="flex h-4 items-center gap-2">
|
|
||||||
<SidebarTrigger className="-ml-1 p-1"/>
|
|
||||||
<Separator orientation="vertical" className="mr-2 h-4"/>
|
|
||||||
{
|
|
||||||
// TODO: implement dynamic Breadcrumbs
|
|
||||||
}
|
|
||||||
<Breadcrumb>
|
|
||||||
<BreadcrumbList>
|
|
||||||
<BreadcrumbItem className="hidden md:block">
|
|
||||||
<BreadcrumbLink href="/">
|
|
||||||
Ory Dashboard
|
|
||||||
</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</BreadcrumbList>
|
|
||||||
</Breadcrumb>
|
|
||||||
</header>
|
|
||||||
<div className="flex-1 overflow-scroll">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</SidebarInset>
|
|
||||||
<Toaster/>
|
|
||||||
</SidebarProvider>
|
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -3,11 +3,11 @@ interface ErrorDisplayProps {
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function ErrorDisplay({ title, message }: ErrorDisplayProps) {
|
export function ErrorDisplay({ title, message }: ErrorDisplayProps) {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<>
|
||||||
<p className="text-3xl font-bold leading-tight tracking-tight">{title}</p>
|
<p className="text-3xl font-bold leading-tight tracking-tight">{title}</p>
|
||||||
<p className="text-lg font-light">{message}</p>
|
<p className="text-lg font-light">{message}</p>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -1,13 +1,13 @@
|
||||||
import { NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { cookies } from 'next/headers';
|
import { cookies } from 'next/headers';
|
||||||
import { getFrontendApi } from '@/ory/sdk/server';
|
import { getFrontendApi, getPermissionApi } from '@/ory/sdk/server';
|
||||||
|
|
||||||
export async function middleware() {
|
export async function middleware(request: NextRequest) {
|
||||||
|
|
||||||
const api = await getFrontendApi();
|
const frontendApi = await getFrontendApi();
|
||||||
const cookie = await cookies();
|
const cookie = await cookies();
|
||||||
|
|
||||||
const session = await api
|
const session = await frontendApi
|
||||||
.toSession({ cookie: 'ory_kratos_session=' + cookie.get('ory_kratos_session')?.value })
|
.toSession({ cookie: 'ory_kratos_session=' + cookie.get('ory_kratos_session')?.value })
|
||||||
.then((response) => response.data)
|
.then((response) => response.data)
|
||||||
.catch(() => null);
|
.catch(() => null);
|
||||||
|
@ -25,7 +25,40 @@ export async function middleware() {
|
||||||
return NextResponse.redirect(url);
|
return NextResponse.redirect(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.next();
|
const permissionApi = await getPermissionApi();
|
||||||
|
const isAdmin = await permissionApi.checkPermission({
|
||||||
|
namespace: 'roles',
|
||||||
|
object: 'admin',
|
||||||
|
relation: 'member',
|
||||||
|
subjectId: session!.identity!.id,
|
||||||
|
})
|
||||||
|
.then(({ data: { allowed } }) => {
|
||||||
|
console.log('is_admin', session!.identity!.id, allowed);
|
||||||
|
return allowed;
|
||||||
|
})
|
||||||
|
.catch((response) => {
|
||||||
|
console.log('is_admin', session!.identity!.id, response, 'check failed');
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isAdmin) {
|
||||||
|
if (request.nextUrl.pathname === '/unauthorised') {
|
||||||
|
return redirect('/', 'HAS PERMISSION BUT ACCESSING /unauthorized');
|
||||||
|
}
|
||||||
|
return NextResponse.next();
|
||||||
|
} else {
|
||||||
|
if (request.nextUrl.pathname === '/unauthorised') {
|
||||||
|
return NextResponse.next();
|
||||||
|
}
|
||||||
|
return redirect('/unauthorised', 'MISSING SESSION');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function redirect(path: string, reason: string) {
|
||||||
|
console.log(reason);
|
||||||
|
const url = `${process.env.NEXT_PUBLIC_DASHBOARD_NODE_URL}${path}`;
|
||||||
|
console.log('REDIRECT TO', url);
|
||||||
|
return NextResponse.redirect(url!);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
import { Configuration, FrontendApi, IdentityApi, MetadataApi, OAuth2Api } from '@ory/client';
|
import {
|
||||||
|
Configuration,
|
||||||
|
FrontendApi,
|
||||||
|
IdentityApi,
|
||||||
|
MetadataApi,
|
||||||
|
OAuth2Api,
|
||||||
|
PermissionApi,
|
||||||
|
RelationshipApi,
|
||||||
|
} from '@ory/client';
|
||||||
|
|
||||||
|
|
||||||
// ####################################################################################
|
// ####################################################################################
|
||||||
|
@ -92,3 +100,57 @@ const kratosMetadataApi = new MetadataApi(
|
||||||
export async function getKratosMetadataApi() {
|
export async function getKratosMetadataApi() {
|
||||||
return kratosMetadataApi;
|
return kratosMetadataApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ####################################################################################
|
||||||
|
// Relationship API
|
||||||
|
// ####################################################################################
|
||||||
|
|
||||||
|
const relationshipApi = new RelationshipApi(new Configuration(
|
||||||
|
{
|
||||||
|
basePath: process.env.ORY_KETO_ADMIN_URL,
|
||||||
|
baseOptions: {
|
||||||
|
withCredentials: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
export async function getRelationshipApi() {
|
||||||
|
return relationshipApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ####################################################################################
|
||||||
|
// Permission API
|
||||||
|
// ####################################################################################
|
||||||
|
|
||||||
|
const permissionApi = new PermissionApi(new Configuration(
|
||||||
|
{
|
||||||
|
basePath: process.env.ORY_KETO_ADMIN_URL,
|
||||||
|
baseOptions: {
|
||||||
|
withCredentials: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
export async function getPermissionApi() {
|
||||||
|
return permissionApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ####################################################################################
|
||||||
|
// Keto Metadata API
|
||||||
|
// ####################################################################################
|
||||||
|
|
||||||
|
const ketoMetadataApi = new MetadataApi(new Configuration(
|
||||||
|
{
|
||||||
|
basePath: process.env.ORY_KETO_ADMIN_URL,
|
||||||
|
baseOptions: {
|
||||||
|
withCredentials: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
export async function getKetoMetadataApi() {
|
||||||
|
return ketoMetadataApi;
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Starting as a container
|
# Starting as a container
|
||||||
|
|
||||||
Starting this project in a container makes testing it really easy. \
|
Starting this project in a container makes testing it really easy.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# move to the environment you want to start (here development)
|
# move to the environment you want to start (here development)
|
||||||
|
@ -17,7 +17,7 @@ sh ./hydra-test-consent.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
These commands will start up multiple containers in the background.
|
These commands will start up multiple containers in the background.
|
||||||
Then continue with starting the authentication UI development server as described in the authentication README.
|
Then continue with starting the authentication UI development server as described in the root README.
|
||||||
|
|
||||||
## Services and Ports
|
## Services and Ports
|
||||||
|
|
||||||
|
@ -28,13 +28,15 @@ If you start up the environment on a remote server, you will need to tunnel the
|
||||||
|
|
||||||
| Service | Port (Public) | Description |
|
| Service | Port (Public) | Description |
|
||||||
|----------------|---------------|---------------------------------------------------------------------------|
|
|----------------|---------------|---------------------------------------------------------------------------|
|
||||||
| Console | 4411 (✗) | Admin dashboard for Kratos data management (soon) |
|
| Console | 4000 (✗) | Admin dashboard for Kratos data management (soon) |
|
||||||
| Authentication | 3000 (✗) | User interface for authentication and account management (no docker yet) |
|
| Authentication | 3000 (✗) | User interface for authentication and account management (no docker yet) |
|
||||||
| ORY Kratos | 4433 (✗) | User management system handling users and self-service flows (Public API) |
|
| Ory Kratos | 4433 (✗) | User management system handling users and self-service flows (Public API) |
|
||||||
| ORY Kratos | 4434 (✗) | User management system handling users and self-service flows (Admin API) |
|
| | 4434 (✗) | User management system handling users and self-service flows (Admin API) |
|
||||||
| Mailslurper | 4436 (✗) | Mock mailing server (Dashboard) |
|
| Mailslurper | 4436 (✗) | Mock mailing server (Dashboard) |
|
||||||
| Mailslurper | 4437 (✗) | Mock mailing server (API) |
|
| | 4437 (✗) | Mock mailing server (API) |
|
||||||
| ORY Hydra | 4444 (✗) | OAuth2 and OIDC server connected to Kratos (Public API) |
|
| Ory Hydra | 4444 (✗) | OAuth2 and OIDC server connected to Kratos (Public API) |
|
||||||
| ORY Hydra | 4445 (✗) | OAuth2 and OIDC server connected to Kratos (Admin API) |
|
| | 4445 (✗) | OAuth2 and OIDC server connected to Kratos (Admin API) |
|
||||||
| ORY Hydra | 5555 (✗) | Hydra test application to test the consent flow |
|
| | 5555 (✗) | Hydra test application to test the consent flow |
|
||||||
|
| Ory Keto | 4466 (✗) | Read Endpoint for Ory Keto authorization ("Public" API) |
|
||||||
|
| | 4467 (✗) | Write Endpoint for Ory Keto authorization ("Admin" API) |
|
||||||
| Postgres DB | 4455 (✗) | Postgres database for storing user data |
|
| Postgres DB | 4455 (✗) | Postgres database for storing user data |
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
# The URL of ORY Hydras admin API
|
# The URL of Ory Hydras admin API
|
||||||
HYDRA_ADMIN_API=http://hydra:4445
|
HYDRA_ADMIN_API=http://hydra:4445
|
||||||
|
|
|
@ -70,6 +70,39 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- internal
|
- internal
|
||||||
|
|
||||||
|
ory-keto-migrate:
|
||||||
|
container_name: ory-keto-migrate
|
||||||
|
image: oryd/keto:v0.12.0
|
||||||
|
restart: on-failure
|
||||||
|
volumes:
|
||||||
|
- ./ory/keto:/etc/config/keto
|
||||||
|
- ory-keto-data:/home/ory
|
||||||
|
- ory-keto-data:/var/lib/sqlite
|
||||||
|
command: migrate -c /etc/config/keto/keto.yaml up --yes
|
||||||
|
depends_on:
|
||||||
|
ory-postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
|
||||||
|
|
||||||
|
ory-keto:
|
||||||
|
container_name: ory-keto
|
||||||
|
image: oryd/keto:v0.12.0
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:4466:4466 # public
|
||||||
|
- 127.0.0.1:4467:4467 # admin
|
||||||
|
volumes:
|
||||||
|
- ./ory/keto:/etc/config/keto
|
||||||
|
- ory-keto-data:/home/ory
|
||||||
|
- ory-keto-data:/var/lib/sqlite
|
||||||
|
command: serve -c /etc/config/keto/keto.yaml all
|
||||||
|
depends_on:
|
||||||
|
ory-keto-migrate:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
networks:
|
||||||
|
- internal
|
||||||
|
|
||||||
ory-mailslurper:
|
ory-mailslurper:
|
||||||
container_name: ory-mailslurper
|
container_name: ory-mailslurper
|
||||||
|
@ -111,4 +144,5 @@ networks:
|
||||||
volumes:
|
volumes:
|
||||||
ory-kratos-data:
|
ory-kratos-data:
|
||||||
ory-hydra-data:
|
ory-hydra-data:
|
||||||
|
ory-keto-data:
|
||||||
ory-postgres-data:
|
ory-postgres-data:
|
||||||
|
|
24
docker/ory-dev/keto-make-admin.sh
Normal file
24
docker/ory-dev/keto-make-admin.sh
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# this script gives the referenced identity the admin role
|
||||||
|
# make sure to provide the id of the identity
|
||||||
|
|
||||||
|
# check if a identity id argument was provided
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Error: please provide an identity id."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# set user id variable
|
||||||
|
IDENTITY_ID=$1
|
||||||
|
|
||||||
|
# execute curl to Ory Keto write endpoint
|
||||||
|
curl --request PUT \
|
||||||
|
--url http://localhost:4467/admin/relation-tuples \
|
||||||
|
--data '{
|
||||||
|
"namespace": "roles",
|
||||||
|
"object": "admin",
|
||||||
|
"relation": "member",
|
||||||
|
"subject_id": "'"$IDENTITY_ID"'"
|
||||||
|
}'
|
||||||
|
|
||||||
|
# write success response to terminal
|
||||||
|
echo "Applied admin role to the user with ID $IDENTITY_ID"
|
43
docker/ory-dev/ory/keto/keto.yaml
Normal file
43
docker/ory-dev/ory/keto/keto.yaml
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
#
|
||||||
|
# Documentation: https://www.ory.sh/docs/keto/reference/configuration
|
||||||
|
# Configuration UI: https://www.ory.sh/docs/keto/reference/configuration-editor
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# Configure the Keto logging
|
||||||
|
#
|
||||||
|
log:
|
||||||
|
level: info
|
||||||
|
format: text
|
||||||
|
leak_sensitive_values: true
|
||||||
|
|
||||||
|
#
|
||||||
|
# Configure the datasource. Alternative for development purposes is 'memory' (not persistent!)
|
||||||
|
#
|
||||||
|
dsn: postgres://postgres:postgres@ory-postgres:5432/keto?sslmode=disable&max_conns=20&max_idle_conns=4
|
||||||
|
|
||||||
|
#
|
||||||
|
# Set the required namespaces
|
||||||
|
#
|
||||||
|
namespaces:
|
||||||
|
- id: 0
|
||||||
|
name: roles
|
||||||
|
|
||||||
|
serve:
|
||||||
|
read:
|
||||||
|
host: 0.0.0.0
|
||||||
|
port: 4466
|
||||||
|
cors:
|
||||||
|
enabled: true
|
||||||
|
allowed_origins:
|
||||||
|
- http://localhost:3000
|
||||||
|
- http://localhost:4000
|
||||||
|
|
||||||
|
write:
|
||||||
|
host: 0.0.0.0
|
||||||
|
port: 4467
|
||||||
|
cors:
|
||||||
|
enabled: true
|
||||||
|
allowed_origins:
|
||||||
|
- http://localhost:3000
|
||||||
|
- http://localhost:4000
|
|
@ -15,9 +15,6 @@
|
||||||
"credentials": {
|
"credentials": {
|
||||||
"password": {
|
"password": {
|
||||||
"identifier": true
|
"identifier": true
|
||||||
},
|
|
||||||
"webauthn": {
|
|
||||||
"identifier": true
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"recovery": {
|
"recovery": {
|
||||||
|
|
|
@ -3,3 +3,6 @@ GRANT ALL PRIVILEGES ON DATABASE kratos TO postgres;
|
||||||
|
|
||||||
CREATE DATABASE hydra;
|
CREATE DATABASE hydra;
|
||||||
GRANT ALL PRIVILEGES ON DATABASE hydra TO postgres;
|
GRANT ALL PRIVILEGES ON DATABASE hydra TO postgres;
|
||||||
|
|
||||||
|
CREATE DATABASE keto;
|
||||||
|
GRANT ALL PRIVILEGES ON DATABASE keto TO postgres;
|
||||||
|
|
Loading…
Add table
Reference in a new issue