Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 469
chore(backend): Improve subject handling for machine auth objects#6099
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
3a774f1343ad53b3e7ac682cd832429c4ab1494ec999260191fa791c00d1e6cb21e16fFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| --- | ||
| "@clerk/backend": minor | ||
| --- | ||
| Improve `subject` property handling for machine auth objects. | ||
| Usage: | ||
| ```ts | ||
| import { createClerkClient } from '@clerk/backend' | ||
| const clerkClient = createClerkClient({ | ||
| secretKey: process.env.CLERK_SECRET_KEY, | ||
| publishableKey: process.env.CLERK_PUBLISHABLE_KEY, | ||
| }) | ||
| const requestState = await clerkClient.authenticateRequest(request, { | ||
| acceptsToken: 'any', | ||
| }) | ||
| const authObject = requestState.toAuth() | ||
| switch (authObject.tokenType) { | ||
| case 'api_key': | ||
| // authObject.userId | ||
| // authObject.orgId | ||
| break; | ||
| case 'machine_token': | ||
| // authObject.machineId | ||
| break; | ||
| case 'oauth_token': | ||
| // authObject.userId | ||
| // authObject.clientId | ||
| break; | ||
| } | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -5,11 +5,11 @@ import { AbstractAPI } from './AbstractApi'; | ||
| const basePath = '/oauth_applications/access_tokens'; | ||
| export class IdPOAuthAccessTokenApi extends AbstractAPI { | ||
| async verifySecret(secret: string) { | ||
| async verifyAccessToken(accessToken: string) { | ||
| ||
| return this.request<IdPOAuthAccessToken>({ | ||
| method: 'POST', | ||
| path: joinPaths(basePath, 'verify'), | ||
| bodyParams: { secret }, | ||
| bodyParams: { access_token: accessToken }, | ||
MemberAuthor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. originally | ||
| }); | ||
| } | ||
wobsoriano marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -280,13 +280,34 @@ describe('authenticatedMachineObject', () => { | ||
| expect(authObject.has({})).toBe(false); | ||
| }); | ||
| it('properly initializes properties', () => { | ||
| it('properly initializes properties (user)', () => { | ||
| const authObject = authenticatedMachineObject('api_key', token, verificationResult, debugData); | ||
| expect(authObject.tokenType).toBe('api_key'); | ||
| expect(authObject.name).toBe('my-api-key'); | ||
| expect(authObject.id).toBe('ak_ey966f1b1xf93586b2debdcadb0b3bd1'); | ||
| expect(authObject.subject).toBe('user_2vYVtestTESTtestTESTtestTESTtest'); | ||
| expect(authObject.scopes).toEqual(['read:foo', 'write:bar']); | ||
| expect(authObject.claims).toEqual({ foo: 'bar' }); | ||
| expect(authObject.userId).toBe('user_2vYVtestTESTtestTESTtestTESTtest'); | ||
| expect(authObject.orgId).toBeNull(); | ||
| }); | ||
| it('properly initializes properties (org)', () => { | ||
| const authObject = authenticatedMachineObject( | ||
| 'api_key', | ||
| token, | ||
| { | ||
| ...verificationResult, | ||
| subject: 'org_2vYVtestTESTtestTESTtestTESTtest', | ||
| }, | ||
| debugData, | ||
| ); | ||
| expect(authObject.tokenType).toBe('api_key'); | ||
| expect(authObject.id).toBe('ak_ey966f1b1xf93586b2debdcadb0b3bd1'); | ||
| expect(authObject.subject).toBe('org_2vYVtestTESTtestTESTtestTESTtest'); | ||
| expect(authObject.scopes).toEqual(['read:foo', 'write:bar']); | ||
| expect(authObject.claims).toEqual({ foo: 'bar' }); | ||
| expect(authObject.userId).toBeNull(); | ||
| expect(authObject.orgId).toBe('org_2vYVtestTESTtestTESTtestTESTtest'); | ||
| }); | ||
| }); | ||
wobsoriano marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| @@ -308,8 +329,11 @@ describe('authenticatedMachineObject', () => { | ||
| it('properly initializes properties', () => { | ||
| const authObject = authenticatedMachineObject('oauth_token', token, verificationResult, debugData); | ||
| expect(authObject.tokenType).toBe('oauth_token'); | ||
| expect(authObject.id).toBe('oat_2VTWUzvGC5UhdJCNx6xG1D98edc'); | ||
| expect(authObject.subject).toBe('user_2vYVtestTESTtestTESTtestTESTtest'); | ||
| expect(authObject.scopes).toEqual(['read:foo', 'write:bar']); | ||
| expect(authObject.userId).toBe('user_2vYVtestTESTtestTESTtestTESTtest'); | ||
| expect(authObject.clientId).toBe('client_2VTWUzvGC5UhdJCNx6xG1D98edc'); | ||
| }); | ||
| }); | ||
| @@ -332,10 +356,11 @@ describe('authenticatedMachineObject', () => { | ||
| it('properly initializes properties', () => { | ||
| const authObject = authenticatedMachineObject('machine_token', token, verificationResult, debugData); | ||
| expect(authObject.tokenType).toBe('machine_token'); | ||
| expect(authObject.name).toBe('my-machine-token'); | ||
| expect(authObject.subject).toBe('user_2vYVtestTESTtestTESTtestTESTtest'); | ||
| expect(authObject.id).toBe('m2m_ey966f1b1xf93586b2debdcadb0b3bd1'); | ||
| expect(authObject.subject).toBe('mch_2vYVtestTESTtestTESTtestTESTtest'); | ||
| expect(authObject.scopes).toEqual(['read:foo', 'write:bar']); | ||
| expect(authObject.claims).toEqual({ foo: 'bar' }); | ||
| expect(authObject.machineId).toBe('mch_2vYVtestTESTtestTESTtestTESTtest'); | ||
| }); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -119,9 +119,9 @@ describe('tokens.verifyMachineAuthToken(token, options)', () => { | ||
| expect(result.errors).toBeUndefined(); | ||
| const data = result.data as MachineToken; | ||
| expect(data.id).toBe('mt_ey966f1b1xf93586b2debdcadb0b3bd1'); | ||
| expect(data.id).toBe('m2m_ey966f1b1xf93586b2debdcadb0b3bd1'); | ||
| expect(data.name).toBe('my-machine-token'); | ||
| expect(data.subject).toBe('user_2vYVtestTESTtestTESTtestTESTtest'); | ||
| expect(data.subject).toBe('mch_2vYVtestTESTtestTESTtestTESTtest'); | ||
| expect(data.scopes).toEqual(['read:foo', 'write:bar']); | ||
wobsoriano marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| expect(data.claims).toEqual({ foo: 'bar' }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -11,7 +11,7 @@ import type { | ||
| SharedSignedInAuthObjectProperties, | ||
| } from '@clerk/types'; | ||
| import type { APIKey, CreateBackendApiOptions, MachineToken } from '../api'; | ||
| import type { APIKey, CreateBackendApiOptions, IdPOAuthAccessToken, MachineToken } from '../api'; | ||
| import { createBackendApiClient } from '../api'; | ||
| import { isTokenTypeAccepted } from '../internal'; | ||
| import type { AuthenticateContext } from './authenticateContext'; | ||
| @@ -84,22 +84,23 @@ export type SignedOutAuthObject = { | ||
| * While all machine token types share common properties (id, name, subject, etc), | ||
| * this type defines the additional properties that are unique to each token type. | ||
| * | ||
| * @example | ||
| * api_key & machine_token: adds `claims` property | ||
| * oauth_token: adds no additional properties (empty object) | ||
| * | ||
| * @template TAuthenticated - Whether the machine object is authenticated or not | ||
| */ | ||
| type MachineObjectExtendedProperties<TAuthenticated extends boolean> = { | ||
| api_key: { | ||
| name: TAuthenticated extends true ? string : null; | ||
| claims: TAuthenticated extends true ? Claims | null : null; | ||
| }; | ||
| api_key: TAuthenticated extends true | ||
| ? | ||
| | { name: string; claims: Claims | null; userId: string; orgId: null } | ||
| | { name: string; claims: Claims | null; userId: null; orgId: string } | ||
| : { name: null; claims: null; userId: null; orgId: null }; | ||
| machine_token: { | ||
| name: TAuthenticated extends true ? string : null; | ||
| claims: TAuthenticated extends true ? Claims | null : null; | ||
| machineId: TAuthenticated extends true ? string : null; | ||
| }; | ||
| oauth_token: { | ||
| userId: TAuthenticated extends true ? string : null; | ||
| clientId: TAuthenticated extends true ? string : null; | ||
| }; | ||
| oauth_token: object; | ||
| }; | ||
| /** | ||
| @@ -257,6 +258,8 @@ export function authenticatedMachineObject<T extends MachineTokenType>( | ||
| name: result.name, | ||
| claims: result.claims, | ||
| scopes: result.scopes, | ||
| userId: result.subject.startsWith('user_') ? result.subject : null, | ||
| orgId: result.subject.startsWith('org_') ? result.subject : null, | ||
| } as unknown as AuthenticatedMachineObject<T>; | ||
wobsoriano marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| case TokenType.MachineToken: { | ||
| @@ -267,14 +270,18 @@ export function authenticatedMachineObject<T extends MachineTokenType>( | ||
| name: result.name, | ||
| claims: result.claims, | ||
| scopes: result.scopes, | ||
| machineId: result.subject, | ||
| } as unknown as AuthenticatedMachineObject<T>; | ||
| } | ||
| case TokenType.OAuthToken: { | ||
| const result = verificationResult as IdPOAuthAccessToken; | ||
| return { | ||
| ...baseObject, | ||
| tokenType, | ||
| scopes: verificationResult.scopes, | ||
| } as AuthenticatedMachineObject<T>; | ||
| scopes: result.scopes, | ||
| userId: result.subject, | ||
| clientId: result.clientId, | ||
| } as unknown as AuthenticatedMachineObject<T>; | ||
| } | ||
| default: | ||
| throw new Error(`Invalid token type: ${tokenType}`); | ||
| @@ -304,6 +311,9 @@ export function unauthenticatedMachineObject<T extends MachineTokenType>( | ||
| tokenType, | ||
| name: null, | ||
| claims: null, | ||
| scopes: null, | ||
| userId: null, | ||
| orgId: null, | ||
| } as unknown as UnauthenticatedMachineObject<T>; | ||
| } | ||
| case TokenType.MachineToken: { | ||
| @@ -312,13 +322,18 @@ export function unauthenticatedMachineObject<T extends MachineTokenType>( | ||
| tokenType, | ||
| name: null, | ||
| claims: null, | ||
| scopes: null, | ||
| machineId: null, | ||
| } as unknown as UnauthenticatedMachineObject<T>; | ||
| } | ||
| case TokenType.OAuthToken: { | ||
| return { | ||
| ...baseObject, | ||
| tokenType, | ||
| } as UnauthenticatedMachineObject<T>; | ||
| scopes: null, | ||
| userId: null, | ||
| clientId: null, | ||
| } as unknown as UnauthenticatedMachineObject<T>; | ||
| } | ||
| default: | ||
| throw new Error(`Invalid token type: ${tokenType}`); | ||
| @@ -406,7 +421,7 @@ export const getAuthObjectFromJwt = ( | ||
| * | ||
| * @example | ||
| * // Accept 'api_key' or 'machine_token' | ||
| * const authObject = { tokenType: 'machine_token', id: 'mt_123' }; | ||
| * const authObject = { tokenType: 'machine_token', id: 'm2m_123' }; | ||
| * const result = getAuthObjectForAcceptedToken({ authObject, acceptsToken: ['api_key', 'machine_token'] }); | ||
| * // result will be the original authObject (since tokenType matches one in the array) | ||
| * | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.