From 10d8fefd57babcf6dd8d9d69458c722eea46407d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?So=CC=88nmez=20Kartal?= Date: Thu, 29 Feb 2024 21:27:11 +0300 Subject: [PATCH] feat(identity): use forward identity urn if present --- apps/passport/app/routes/authorize.tsx | 2 +- packages/platform-middleware/jwt.ts | 13 +++++++++-- .../src/jsonrpc/methods/deleteAccountNode.ts | 13 +++++++++-- .../jsonrpc/methods/getAuthorizedAppScopes.ts | 11 ++++++++- .../src/jsonrpc/methods/getPersonaData.ts | 13 +++++++++-- .../src/jsonrpc/methods/preauthorize.ts | 15 +++++++++--- .../src/jsonrpc/methods/revokeToken.ts | 8 ++++++- .../src/jsonrpc/methods/verifyToken.ts | 8 ++++++- .../middleware/setAuthorizationNode.ts | 9 ++++++-- .../hasIdentityGroupPermissions.ts | 16 ++++++++++--- platform/identity/src/nodes/identity-group.ts | 23 ++++++++++++++----- 11 files changed, 107 insertions(+), 24 deletions(-) diff --git a/apps/passport/app/routes/authorize.tsx b/apps/passport/app/routes/authorize.tsx index 22d0b3a717..09bcd52a20 100644 --- a/apps/passport/app/routes/authorize.tsx +++ b/apps/passport/app/routes/authorize.tsx @@ -238,7 +238,7 @@ export const loader: LoaderFunction = getRollupReqFunctionErrorWrapper( const responseType = ResponseType.Code const preauthorizeRes = await coreClient.authorization.preauthorize.mutate({ - identity: identityURN, + identityURN, responseType, clientId, redirectUri, diff --git a/packages/platform-middleware/jwt.ts b/packages/platform-middleware/jwt.ts index d9170356c3..6a2731e5ad 100644 --- a/packages/platform-middleware/jwt.ts +++ b/packages/platform-middleware/jwt.ts @@ -3,6 +3,9 @@ import { IdentityURNSpace, type IdentityURN } from '@proofzero/urns/identity' import { getAuthzTokenFromReq } from '@proofzero/utils' import { checkToken } from '@proofzero/utils/token' +import { type Environment } from '@proofzero/platform.core/src/types' +import { initIdentityNodeByName } from '@proofzero/platform.identity/src/nodes' + import { BaseMiddlewareFunction } from './types' export const AuthorizationTokenFromHeader: BaseMiddlewareFunction<{ @@ -20,15 +23,21 @@ export const AuthorizationTokenFromHeader: BaseMiddlewareFunction<{ } export const ValidateJWT: BaseMiddlewareFunction<{ + env: Environment token?: string -}> = ({ ctx, next }) => { +}> = async ({ ctx, next }) => { if (ctx.token) { const { sub: subject } = checkToken(ctx.token) if (subject && IdentityURNSpace.is(subject)) { + const identityNode = initIdentityNodeByName(subject, ctx.env.Identity) + const forwardIdentityURN = + await identityNode.class.getForwardIdentityURN() + const identityURN = forwardIdentityURN || subject + return next({ ctx: { ...ctx, - identityURN: subject, + identityURN, }, }) } diff --git a/platform/account/src/jsonrpc/methods/deleteAccountNode.ts b/platform/account/src/jsonrpc/methods/deleteAccountNode.ts index a027953f74..101a0d4ba0 100644 --- a/platform/account/src/jsonrpc/methods/deleteAccountNode.ts +++ b/platform/account/src/jsonrpc/methods/deleteAccountNode.ts @@ -7,6 +7,7 @@ import { RollupError, ERROR_CODES } from '@proofzero/errors' import { IdentityURNSpace } from '@proofzero/urns/identity' import { AccountURN } from '@proofzero/urns/account' +import { initIdentityNodeByName } from '@proofzero/platform.identity/src/nodes' import { GetEdgesMethodOutput } from '@proofzero/platform.edges/src/jsonrpc/methods/getEdges' import type { Context } from '../../context' @@ -28,12 +29,20 @@ export const deleteAccountNodeMethod = async ({ input: DeleteAccountNodeParams ctx: Context }) => { - const { identityURN, forceDelete } = input + const { forceDelete } = input - if (!IdentityURNSpace.is(identityURN)) throw new Error('Invalid identity URN') + if (!IdentityURNSpace.is(input.identityURN)) + throw new Error('Invalid identity URN') if (!ctx.accountURN) throw new Error('missing account URN') if (!ctx.account) throw new Error('missing account node') + const identityNode = initIdentityNodeByName( + input.identityURN, + ctx.env.Identity + ) + const forwardIdentityURN = await identityNode.class.getForwardIdentityURN() + const identityURN = forwardIdentityURN || input.identityURN + const caller = router.createCaller(ctx) if (!forceDelete) { diff --git a/platform/authorization/src/jsonrpc/methods/getAuthorizedAppScopes.ts b/platform/authorization/src/jsonrpc/methods/getAuthorizedAppScopes.ts index 5ab92d19d4..5630c04937 100644 --- a/platform/authorization/src/jsonrpc/methods/getAuthorizedAppScopes.ts +++ b/platform/authorization/src/jsonrpc/methods/getAuthorizedAppScopes.ts @@ -7,6 +7,8 @@ import { IdentityURNSpace } from '@proofzero/urns/identity' import { appRouter } from '../router' import { getClaimValues } from '@proofzero/security/persona' +import { initIdentityNodeByName } from '@proofzero/platform.identity/src/nodes' + export const GetAuthorizedAppScopesMethodInput = z.object({ identityURN: inputValidators.IdentityURNInput, clientId: z.string().min(1), @@ -47,7 +49,14 @@ export const getAuthorizedAppScopesMethod = async ({ input: GetAuthorizedAppScopesMethodParams ctx: Context }): Promise => { - const { identityURN, clientId } = input + const { clientId } = input + + const identityNode = initIdentityNodeByName( + input.identityURN, + ctx.env.Identity + ) + const forwardIdentityURN = await identityNode.class.getForwardIdentityURN() + const identityURN = forwardIdentityURN || input.identityURN const nss = `${IdentityURNSpace.decode(identityURN)}@${clientId}` const urn = AuthorizationURNSpace.componentizedUrn(nss) diff --git a/platform/authorization/src/jsonrpc/methods/getPersonaData.ts b/platform/authorization/src/jsonrpc/methods/getPersonaData.ts index d321b42700..fdd3039e80 100644 --- a/platform/authorization/src/jsonrpc/methods/getPersonaData.ts +++ b/platform/authorization/src/jsonrpc/methods/getPersonaData.ts @@ -4,6 +4,8 @@ import { BadRequestError } from '@proofzero/errors' import { AuthorizationURNSpace } from '@proofzero/urns/authorization' import { IdentityURNSpace } from '@proofzero/urns/identity' +import { initIdentityNodeByName } from '@proofzero/platform.identity/src/nodes' + import { Context } from '../../context' import { initAuthorizationNodeByName } from '../../nodes' import { PersonaData } from '@proofzero/types/application' @@ -22,18 +24,25 @@ export const getPersonaDataMethod = async ({ input: z.infer ctx: Context }): Promise> => { - const { identityURN, clientId } = input + const { clientId } = input if (!clientId) throw new BadRequestError({ message: 'missing client id', }) - if (!IdentityURNSpace.is(identityURN)) + if (!IdentityURNSpace.is(input.identityURN)) throw new BadRequestError({ message: 'missing identity', }) + const identityNode = initIdentityNodeByName( + input.identityURN, + ctx.env.Identity + ) + const forwardIdentityURN = await identityNode.class.getForwardIdentityURN() + const identityURN = forwardIdentityURN || input.identityURN + const nss = `${IdentityURNSpace.decode(identityURN)}@${clientId}` const urn = AuthorizationURNSpace.componentizedUrn(nss) const authorizationNode = initAuthorizationNodeByName( diff --git a/platform/authorization/src/jsonrpc/methods/preauthorize.ts b/platform/authorization/src/jsonrpc/methods/preauthorize.ts index a3dcc64bda..c22737e2ab 100644 --- a/platform/authorization/src/jsonrpc/methods/preauthorize.ts +++ b/platform/authorization/src/jsonrpc/methods/preauthorize.ts @@ -9,8 +9,10 @@ import { appRouter } from '../router' import { AuthorizationURNSpace } from '@proofzero/urns/authorization' import { IdentityURNSpace } from '@proofzero/urns/identity' +import { initIdentityNodeByName } from '@proofzero/platform.identity/src/nodes' + export const PreAuthorizeMethodInput = z.object({ - identity: IdentityURNInput, + identityURN: IdentityURNInput, responseType: z.string(), clientId: z.string(), redirectUri: z.string(), @@ -42,9 +44,16 @@ export const preauthorizeMethod = async ({ }): Promise => { let preauthorized = false - const { identity, clientId, scope: requestedScope } = input + const { clientId, scope: requestedScope } = input + + const identityNode = initIdentityNodeByName( + input.identityURN, + ctx.env.Identity + ) + const forwardIdentityURN = await identityNode.class.getForwardIdentityURN() + const identityURN = forwardIdentityURN || input.identityURN - const nss = `${IdentityURNSpace.decode(identity)}@${clientId}` + const nss = `${IdentityURNSpace.decode(identityURN)}@${clientId}` const urn = AuthorizationURNSpace.componentizedUrn(nss) const authorizationNode = initAuthorizationNodeByName( urn, diff --git a/platform/authorization/src/jsonrpc/methods/revokeToken.ts b/platform/authorization/src/jsonrpc/methods/revokeToken.ts index bb54c1c04f..d141ea90da 100644 --- a/platform/authorization/src/jsonrpc/methods/revokeToken.ts +++ b/platform/authorization/src/jsonrpc/methods/revokeToken.ts @@ -6,6 +6,8 @@ import type { AuthorizationJWTPayload } from '@proofzero/types/authorization' import { AuthorizationURNSpace } from '@proofzero/urns/authorization' import { IdentityURNSpace } from '@proofzero/urns/identity' +import { initIdentityNodeByName } from '@proofzero/platform.identity/src/nodes' + import { Context } from '../../context' import { getJWKS } from '../../jwk' import { initAuthorizationNodeByName } from '../../nodes' @@ -51,7 +53,11 @@ export const revokeTokenMethod: RevokeTokenMethod = async ({ ctx, input }) => { if (clientId != payload.aud[0]) throw MismatchClientIdError if (!payload.sub) throw MissingSubjectError - const identityURN = payload.sub + let identityURN = payload.sub + const identityNode = initIdentityNodeByName(identityURN, ctx.env.Identity) + const forwardIdentityURN = await identityNode.class.getForwardIdentityURN() + if (forwardIdentityURN) identityURN = forwardIdentityURN + const nss = `${IdentityURNSpace.decode(identityURN)}@${clientId}` const urn = AuthorizationURNSpace.componentizedUrn(nss) const node = initAuthorizationNodeByName(urn, ctx.env.Authorization) diff --git a/platform/authorization/src/jsonrpc/methods/verifyToken.ts b/platform/authorization/src/jsonrpc/methods/verifyToken.ts index 472932bf7d..09d766be55 100644 --- a/platform/authorization/src/jsonrpc/methods/verifyToken.ts +++ b/platform/authorization/src/jsonrpc/methods/verifyToken.ts @@ -7,6 +7,8 @@ import { AuthorizationURNSpace } from '@proofzero/urns/authorization' import { IdentityURNSpace } from '@proofzero/urns/identity' import { getErrorCause } from '@proofzero/utils/errors' +import { initIdentityNodeByName } from '@proofzero/platform.identity/src/nodes' + import { Context } from '../../context' import { getJWKS } from '../../jwk' import { initAuthorizationNodeByName } from '../../nodes' @@ -58,7 +60,11 @@ export const verifyTokenMethod: VerifyTokenMethod = async ({ ctx, input }) => { if (clientId && clientId != payload.aud[0]) throw MismatchClientIdError if (!payload.sub) throw MissingSubjectError - const identityURN = payload.sub + let identityURN = payload.sub + const identityNode = initIdentityNodeByName(identityURN, ctx.env.Identity) + const forwardIdentityURN = await identityNode.class.getForwardIdentityURN() + if (forwardIdentityURN) identityURN = forwardIdentityURN + const nss = `${IdentityURNSpace.decode(identityURN)}@${clientId}` const urn = AuthorizationURNSpace.componentizedUrn(nss) const node = initAuthorizationNodeByName(urn, ctx.env.Authorization) diff --git a/platform/authorization/src/jsonrpc/middleware/setAuthorizationNode.ts b/platform/authorization/src/jsonrpc/middleware/setAuthorizationNode.ts index bf8adca993..6a35379b41 100644 --- a/platform/authorization/src/jsonrpc/middleware/setAuthorizationNode.ts +++ b/platform/authorization/src/jsonrpc/middleware/setAuthorizationNode.ts @@ -5,6 +5,8 @@ import { IdentityURNSpace } from '@proofzero/urns/identity' import { AuthorizationJWTPayload } from '@proofzero/types/authorization' import { BaseMiddlewareFunction } from '@proofzero/platform-middleware/types' +import { initIdentityNodeByName } from '@proofzero/platform.identity/src/nodes' + import { initAuthorizationNodeByName } from '../../nodes' import { Context } from '../../context' @@ -17,17 +19,20 @@ export const setAuthorizationNode: BaseMiddlewareFunction = async ({ if (!ctx.token) throw new Error('No token found in middleware context') const jwt = decodeJwt(ctx.token) as AuthorizationJWTPayload - const identityURN = jwt.sub const [clientId] = jwt.aud if (!clientId) { throw new Error('missing client id in the aud claim') } - if (!IdentityURNSpace.is(identityURN)) { + if (!IdentityURNSpace.is(jwt.sub)) { throw new Error(`missing identity in the sub claim`) } + const identityNode = initIdentityNodeByName(jwt.sub, ctx.env.Identity) + const forwardIdentityURN = await identityNode.class.getForwardIdentityURN() + const identityURN = forwardIdentityURN || jwt.sub + const nss = `${IdentityURNSpace.decode(identityURN)}@${clientId}` const urn = AuthorizationURNSpace.componentizedUrn(nss) const authorizationNode = initAuthorizationNodeByName( diff --git a/platform/identity/src/jsonrpc/methods/identity-groups/hasIdentityGroupPermissions.ts b/platform/identity/src/jsonrpc/methods/identity-groups/hasIdentityGroupPermissions.ts index fd157bbca7..cbd87c275c 100644 --- a/platform/identity/src/jsonrpc/methods/identity-groups/hasIdentityGroupPermissions.ts +++ b/platform/identity/src/jsonrpc/methods/identity-groups/hasIdentityGroupPermissions.ts @@ -7,7 +7,10 @@ import { router } from '@proofzero/platform.core' import { EDGE_MEMBER_OF_IDENTITY_GROUP } from '@proofzero/types/graph' import { Context } from '../../../context' -import { initIdentityGroupNodeByName } from '../../../nodes' +import { + initIdentityNodeByName, + initIdentityGroupNodeByName, +} from '../../../nodes' export const HasIdentityGroupPermissionsInputSchema = z.object({ identityURN: IdentityURNInput, @@ -34,10 +37,17 @@ export const hasIdentityGroupPermissions = async ({ }): Promise => { const caller = router.createCaller(ctx) + const identityNode = initIdentityNodeByName( + input.identityURN, + ctx.env.Identity + ) + const forwardIdentityURN = await identityNode.class.getForwardIdentityURN() + const identityURN = forwardIdentityURN || input.identityURN + const { edges } = await caller.edges.getEdges({ query: { src: { - baseUrn: input.identityURN, + baseUrn: identityURN, }, tag: EDGE_MEMBER_OF_IDENTITY_GROUP, dst: { @@ -50,7 +60,7 @@ export const hasIdentityGroupPermissions = async ({ input.identityGroupURN, ctx.env.IdentityGroup ) - const { error } = await DO.class.validateAdmin(input.identityURN) + const { error } = await DO.class.validateAdmin(identityURN) return { read: edges.length > 0, diff --git a/platform/identity/src/nodes/identity-group.ts b/platform/identity/src/nodes/identity-group.ts index 3bd249e4f0..c718589d02 100644 --- a/platform/identity/src/nodes/identity-group.ts +++ b/platform/identity/src/nodes/identity-group.ts @@ -17,6 +17,10 @@ import { import { IdentityURN } from '@proofzero/urns/identity' import { DOProxy } from 'do-proxy' import { NodeMethodReturnValue } from '@proofzero/types/node' + +import { Environment } from '@proofzero/platform.core' + +import { initIdentityNodeByName } from '.' import { IDENTITY_GROUP_OPTIONS } from '../constants' export type InviteMemberInput = { @@ -36,10 +40,12 @@ export type ClearInvitationInput = { export default class IdentityGroup extends DOProxy { declare state: DurableObjectState + declare env: Environment - constructor(state: DurableObjectState) { - super(state) + constructor(state: DurableObjectState, env: Environment) { + super(state, env) this.state = state + this.env = env } async getServicePlans(): Promise { @@ -189,11 +195,16 @@ export default class IdentityGroup extends DOProxy { } async getOrderedMembers(): Promise { - const orderedMembers = await this.state.storage.get( - 'orderedMembers' + const orderedMembers = + (await this.state.storage.get('orderedMembers')) || [] + + return Promise.all( + orderedMembers.map(async (urn) => { + const node = initIdentityNodeByName(urn, this.env.Identity) + const forwardURN = await node.class.getForwardIdentityURN() + return forwardURN || urn + }) ) - - return orderedMembers || [] } async setOrderedMembers(members: IdentityURN[]): Promise {