1
0
Fork 0
mirror of https://codeberg.org/MarkusThielker/next-ory.git synced 2025-04-19 09:01:18 +00:00

NORY-59: refactor identity queries to use server actions

This commit is contained in:
Markus Thielker 2025-04-06 13:02:56 +02:00
parent 8d10b744e9
commit c2299f0340
3 changed files with 123 additions and 73 deletions

View file

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import { getIdentityApi } from '@/ory/sdk/server';
import { ErrorDisplay } from '@/components/error'; import { ErrorDisplay } from '@/components/error';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { IdentityTraits } from '@/components/identity/identity-traits'; import { IdentityTraits } from '@/components/identity/identity-traits';
@ -14,6 +13,8 @@ import { IdentityCredentials } from '@/components/identity/identity-credentials'
import { checkPermission, requirePermission, requireSession } from '@/lib/action/authentication'; import { checkPermission, requirePermission, requireSession } from '@/lib/action/authentication';
import { permission, relation } from '@/lib/permission'; import { permission, relation } from '@/lib/permission';
import { redirect } from 'next/navigation'; import { redirect } from 'next/navigation';
import InsufficientPermission from '@/components/insufficient-permission';
import { getIdentity, getIdentitySchema, listIdentitySessions } from '@/lib/action/identity';
interface MergedAddress { interface MergedAddress {
recovery_id?: string; recovery_id?: string;
@ -91,65 +92,65 @@ export default async function UserDetailsPage({ params }: { params: Promise<{ id
const pmEditUser = await checkPermission(permission.user.it, relation.edit, identityId); const pmEditUser = await checkPermission(permission.user.it, relation.edit, identityId);
const pmDeleteUser = await checkPermission(permission.user.it, relation.delete, identityId); const pmDeleteUser = await checkPermission(permission.user.it, relation.delete, identityId);
const pmAccessUserTraits = await checkPermission(permission.user.trait, relation.access, identityId);
const pmEditUserState = await checkPermission(permission.user.state, relation.edit, identityId); const pmEditUserState = await checkPermission(permission.user.state, relation.edit, identityId);
const pmAccessUserSession = await checkPermission(permission.user.session, relation.access, identityId);
const pmDeleteUserSession = await checkPermission(permission.user.session, relation.delete, identityId); const pmDeleteUserSession = await checkPermission(permission.user.session, relation.delete, identityId);
const pmCreateUserCode = await checkPermission(permission.user.code, relation.create, identityId); const pmCreateUserCode = await checkPermission(permission.user.code, relation.create, identityId);
const pmCreateUserLink = await checkPermission(permission.user.link, relation.create, identityId); const pmCreateUserLink = await checkPermission(permission.user.link, relation.create, identityId);
const detailIdentityId = (await params).id; const detailIdentityId = (await params).id;
const detailIdentity = pmAccessUser && await getIdentity(detailIdentityId);
const identityApi = await getIdentityApi(); if (!detailIdentity) {
const identity = await identityApi.getIdentity({ id: detailIdentityId })
.then((response) => {
return response.data;
})
.catch(() => {
console.log('Identity not found');
});
const sessions = await identityApi.listIdentitySessions({ id: detailIdentityId })
.then((response) => response.data)
.catch(() => {
console.log('No sessions found');
});
if (!identity) {
return <ErrorDisplay return <ErrorDisplay
title="Identity not found" title="Identity not found"
message={`The requested identity with id ${detailIdentityId} does not exist`}/>; message={`The requested identity with id ${detailIdentityId} does not exist`}/>;
} }
if (!identity.verifiable_addresses || !identity.verifiable_addresses[0]) { if (!detailIdentity.verifiable_addresses || !detailIdentity.verifiable_addresses[0]) {
return <ErrorDisplay return <ErrorDisplay
title="No verifiable adress" title="No verifiable adress"
message="The identity you are trying to see exists but has no identifiable address"/>; message="The identity you are trying to see exists but has no identifiable address"/>;
} }
const identitySchema = await identityApi
.getIdentitySchema({ id: identity.schema_id }) const detailIdentitySessions = pmAccessUserSession && await listIdentitySessions(detailIdentityId);
.then((response) => response.data as KratosSchema);
const detailIdentitySchema = await getIdentitySchema(detailIdentity.schema_id)
.then((response) => response as KratosSchema);
const addresses = mergeAddresses( const addresses = mergeAddresses(
identity.recovery_addresses ?? [], detailIdentity.recovery_addresses ?? [],
identity.verifiable_addresses ?? [], detailIdentity.verifiable_addresses ?? [],
); );
return ( return (
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<p className="text-3xl font-bold leading-tight tracking-tight">{addresses[0].value}</p> <p className="text-3xl font-bold leading-tight tracking-tight">{addresses[0].value}</p>
<p className="text-lg font-light">{identity.id}</p> <p className="text-lg font-light">{detailIdentity.id}</p>
</div> </div>
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4"> <div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
<Card className="row-span-3"> {
<CardHeader> pmAccessUserTraits ?
<CardTitle>Traits</CardTitle> <Card className="row-span-3">
<CardDescription>All identity properties specified in the identity schema</CardDescription> <CardHeader>
</CardHeader> <CardTitle>Traits</CardTitle>
<CardContent> <CardDescription>All identity properties specified in the identity
<IdentityTraits schema={identitySchema} identity={identity}/> schema</CardDescription>
</CardContent> </CardHeader>
</Card> <CardContent>
<IdentityTraits schema={detailIdentitySchema} identity={detailIdentity}/>
</CardContent>
</Card>
:
<InsufficientPermission
permission={permission.user.trait}
relation={relation.access}
identityId={identityId}
classNames="row-span-3"/>
}
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Actions</CardTitle> <CardTitle>Actions</CardTitle>
@ -157,7 +158,7 @@ export default async function UserDetailsPage({ params }: { params: Promise<{ id
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<IdentityActions <IdentityActions
identity={identity} identity={detailIdentity}
permissions={{ permissions={{
pmEditUser, pmEditUser,
pmDeleteUser, pmDeleteUser,
@ -220,7 +221,7 @@ export default async function UserDetailsPage({ params }: { params: Promise<{ id
<CardDescription>All authentication mechanisms registered with this identity</CardDescription> <CardDescription>All authentication mechanisms registered with this identity</CardDescription>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<IdentityCredentials identity={identity}/> <IdentityCredentials identity={detailIdentity}/>
</CardContent> </CardContent>
</Card> </Card>
<Card> <Card>
@ -229,46 +230,48 @@ export default async function UserDetailsPage({ params }: { params: Promise<{ id
<CardDescription>See and manage all sessions of this identity</CardDescription> <CardDescription>See and manage all sessions of this identity</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<Table> {
<TableHeader> detailIdentitySessions ?
<TableRow> <Table>
<TableHead>OS</TableHead> <TableHeader>
<TableHead>Browser</TableHead> <TableRow>
<TableHead>Active since</TableHead> <TableHead>OS</TableHead>
</TableRow> <TableHead>Browser</TableHead>
</TableHeader> <TableHead>Active since</TableHead>
<TableBody> </TableRow>
{ </TableHeader>
sessions ? <TableBody>
sessions.map((session) => { {
detailIdentitySessions.map((session) => {
const device = session.devices![0]; const device = session.devices![0];
const parser = new UAParser(device.user_agent); const parser = new UAParser(device.user_agent);
const result = parser.getResult(); const result = parser.getResult();
return ( return (
<TableRow key={session.id}> <TableRow key={session.id}>
<TableCell className="space-x-1"> <TableCell className="space-x-1">
<span>{result.os.name}</span> <span>{result.os.name}</span>
<span <span
className="text-xs text-neutral-500">{result.os.version}</span> className="text-xs text-neutral-500">{result.os.version}</span>
</TableCell> </TableCell>
<TableCell className="space-x-1"> <TableCell className="space-x-1">
<span>{result.browser.name}</span> <span>{result.browser.name}</span>
<span <span
className="text-xs text-neutral-500">{result.browser.version}</span> className="text-xs text-neutral-500">{result.browser.version}</span>
</TableCell> </TableCell>
<TableCell> <TableCell>
{new Date(session.authenticated_at!).toLocaleString()} {new Date(session.authenticated_at!).toLocaleString()}
</TableCell> </TableCell>
</TableRow> </TableRow>
); );
}) })
: }
<ErrorDisplay title="No sessions" message=""/> </TableBody>
} </Table>
</TableBody> :
</Table> <p>This user has no active sessions</p>
}
</CardContent> </CardContent>
</Card> </Card>
</div> </div>

View file

@ -15,6 +15,34 @@ import { eq, ilike, or, sql } from 'drizzle-orm';
import { checkPermission, requireSession } from '@/lib/action/authentication'; import { checkPermission, requireSession } from '@/lib/action/authentication';
import { permission, relation } from '@/lib/permission'; import { permission, relation } from '@/lib/permission';
export async function getIdentity(id: string) {
const session = await requireSession();
const allowed = await checkPermission(permission.user.it, relation.access, session.identity!.id);
if (!allowed) {
throw Error('Unauthorised');
}
const identityApi = await getIdentityApi();
const { data } = await identityApi.getIdentity({ id });
console.log('Got identity', data);
return data;
}
export async function getIdentitySchema(id: string) {
const identityApi = await getIdentityApi();
const { data } = await identityApi.getIdentitySchema({ id: id });
console.log('Got identity schema');
return data;
}
interface QueryIdentitiesProps { interface QueryIdentitiesProps {
page: number, page: number,
pageSize: number, pageSize: number,
@ -131,6 +159,24 @@ export async function deleteIdentityCredential({ id, type }: DeleteIdentityCrede
return data; return data;
} }
export async function listIdentitySessions(id: string) {
const session = await requireSession();
const allowed = await checkPermission(permission.user.session, relation.access, session.identity!.id);
if (!allowed) {
throw Error('Unauthorised');
}
const identityApi = await getIdentityApi();
const { data } = await identityApi.listIdentitySessions({ id });
console.log('Listed identity\'s sessions', data);
revalidatePath('/user');
return data;
}
export async function deleteIdentitySessions(id: string) { export async function deleteIdentitySessions(id: string) {
const session = await requireSession(); const session = await requireSession();

View file

@ -10,6 +10,7 @@ export const permission = {
link: 'admin.user.link', link: 'admin.user.link',
session: 'admin.user.session', session: 'admin.user.session',
state: 'admin.user.state', state: 'admin.user.state',
trait: 'admin.user.trait',
}, },
}; };