Skip to content

feat(backend): WIP M2M #5564

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

Draft
wants to merge 55 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
5863e50
feat: Add BAPI types for M2M Tokens
wobsoriano Apr 8, 2025
783a154
chore: add M2MToken factory
wobsoriano Apr 8, 2025
1465e72
chore: Fix incorrect optional fields
wobsoriano Apr 8, 2025
1225b7a
chore: standardize Machine token naming convention
wobsoriano Apr 8, 2025
cf871fb
feat: Initial M2M verification implementation
wobsoriano Apr 9, 2025
ab2f50d
chore: fix entity types
wobsoriano Apr 9, 2025
c6019c7
chore: use backend api client for requests
wobsoriano Apr 9, 2025
35035cf
chore: small updates
wobsoriano Apr 9, 2025
80a5937
chore: account for other machine tokens
wobsoriano Apr 9, 2025
59cdae8
chore: some typings
wobsoriano Apr 9, 2025
40ae925
chore: some more to-do types
wobsoriano Apr 9, 2025
1384ed8
chore: clean up machine token conditions
wobsoriano Apr 10, 2025
0bbb674
chore: some updates
wobsoriano Apr 10, 2025
e71a1e8
chore: some fixes
wobsoriano Apr 11, 2025
d29288a
chore: add error handling
wobsoriano Apr 11, 2025
22459f9
chore: Add overload function to factory
wobsoriano Apr 11, 2025
0da04c2
chore: Introduce request state typeguards
wobsoriano Apr 11, 2025
af555ef
fix previous commit export type
wobsoriano Apr 12, 2025
ae1eb31
chore: remove accidental install
wobsoriano Apr 12, 2025
be71f18
chore: type improvements
wobsoriano Apr 12, 2025
d9a75e5
chore: rename verification methods
wobsoriano Apr 14, 2025
2217c77
chore: rename oauth applications casing
wobsoriano Apr 14, 2025
f509e9e
Merge branch 'main' into rob/robo-36-sdk-implement-support-for-m2m-to…
wobsoriano Apr 14, 2025
712c730
chore: type fixes and merge conflicts
wobsoriano Apr 14, 2025
b264153
chore: add super initial test
wobsoriano Apr 15, 2025
df77c8d
chore: clean up
wobsoriano Apr 15, 2025
61d7f99
chore: revert Oauth casing
wobsoriano Apr 15, 2025
17504b1
chore: refactor to new DX with separate machine entities
wobsoriano Apr 15, 2025
d19c73c
chore: handle to auth incosistent entities
wobsoriano Apr 15, 2025
c0ea085
chore: handle any entity in toAuth
wobsoriano Apr 15, 2025
339f9bb
chore: get to a snapshottable state
wobsoriano Apr 16, 2025
1df4bcd
Merge branch 'main' into rob/robo-36-sdk-implement-support-for-m2m-to…
wobsoriano Apr 16, 2025
a0fa59f
chore: rename entity option to accept and do not throw mismatch tokens
wobsoriano Apr 16, 2025
57d5ee7
chore: add serializer for machine tokens
wobsoriano Apr 16, 2025
ea445de
chore: update oauth machine classes
wobsoriano Apr 16, 2025
15bd90e
chore: update token option name
wobsoriano Apr 16, 2025
77aae2f
Merge branch 'main' into rob/robo-36-sdk-implement-support-for-m2m-to…
wobsoriano Apr 17, 2025
ab2d4f6
chore: DX update
wobsoriano Apr 18, 2025
d7edcb9
chore: set session_token as default request state generic
wobsoriano Apr 18, 2025
0ba1bc6
chore: better handling of token type mismatches
wobsoriano Apr 18, 2025
897f270
chore: clean up non session token types
wobsoriano Apr 18, 2025
fdb236c
chore: introduce isAuthenticated
wobsoriano Apr 18, 2025
a4e8d15
chore: some more updates
wobsoriano Apr 19, 2025
00c86fa
chore: simplified
wobsoriano Apr 19, 2025
4dcc0a8
chore: make array of token types unique
wobsoriano Apr 19, 2025
120da73
chore: add initial tests
wobsoriano Apr 20, 2025
cc08f19
chore: deprecate SignedInState and SignedOutState
wobsoriano Apr 20, 2025
1f3e663
test: add auth object tests
wobsoriano Apr 20, 2025
f671b70
chore: authObject test
wobsoriano Apr 21, 2025
cc2a664
chore: add auth status tests for machine auth tokens
wobsoriano Apr 21, 2025
86cdb12
test: add machine token verification tests
wobsoriano Apr 21, 2025
d2a0acc
test: add request tests
wobsoriano Apr 21, 2025
25007bc
test: clean up request test
wobsoriano Apr 21, 2025
ce00d8c
Merge branch 'main' into rob/robo-36-sdk-implement-support-for-m2m-to…
wobsoriano Apr 22, 2025
8503eda
chore: clean up overloads
wobsoriano Apr 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions packages/agent-toolkit/src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { AuthObject, ClerkClient } from '@clerk/backend';
import type { ClerkClient } from '@clerk/backend';
import type { SignedInAuthObject, SignedOutAuthObject } from '@clerk/backend/internal';

import type { ClerkTool } from './clerk-tool';

Expand All @@ -12,7 +13,7 @@ export type ToolkitParams = {
* @default {}
*/
authContext?: Pick<
AuthObject,
SignedInAuthObject | SignedOutAuthObject,
'userId' | 'sessionId' | 'sessionClaims' | 'orgId' | 'orgRole' | 'orgSlug' | 'orgPermissions' | 'actor'
>;
/**
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/__tests__/exports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ describe('subpath /errors exports', () => {
it('should not include a breaking change', () => {
expect(Object.keys(errorExports).sort()).toMatchInlineSnapshot(`
[
"MachineTokenVerificationError",
"MachineTokenVerificationErrorCode",
"SignJWTError",
"TokenVerificationError",
"TokenVerificationErrorAction",
Expand Down
15 changes: 15 additions & 0 deletions packages/backend/src/api/endpoints/APIKeysApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { joinPaths } from '../../util/path';
import type { APIKey } from '../resources/APIKey';
import { AbstractAPI } from './AbstractApi';

const basePath = '/api_keys';

export class APIKeysAPI extends AbstractAPI {
async verifySecret(secret: string) {
return this.request<APIKey>({
method: 'POST',
path: joinPaths(basePath, 'verify'),
bodyParams: { secret },
});
}
}
15 changes: 15 additions & 0 deletions packages/backend/src/api/endpoints/IdPOAuthAccessTokenApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { joinPaths } from '../../util/path';
import type { IdPOAuthAccessToken } from '../resources';
import { AbstractAPI } from './AbstractApi';

const basePath = '/oauth_applications/access_tokens';

export class IdPOAuthAccessTokenApi extends AbstractAPI {
async verifySecret(secret: string) {
return this.request<IdPOAuthAccessToken>({
method: 'POST',
path: joinPaths(basePath, 'verify'),
bodyParams: { secret },
});
}
}
15 changes: 15 additions & 0 deletions packages/backend/src/api/endpoints/MachineTokensApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { joinPaths } from '../../util/path';
import type { MachineToken } from '../resources/MachineToken';
import { AbstractAPI } from './AbstractApi';

const basePath = '/m2m_tokens';

export class MachineTokensApi extends AbstractAPI {
async verifySecret(secret: string) {
return this.request<MachineToken>({
method: 'POST',
path: joinPaths(basePath, 'verify'),
bodyParams: { secret },
});
}
}
3 changes: 3 additions & 0 deletions packages/backend/src/api/endpoints/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ export * from './ActorTokenApi';
export * from './AccountlessApplicationsAPI';
export * from './AbstractApi';
export * from './AllowlistIdentifierApi';
export * from './APIKeysApi';
export * from './BetaFeaturesApi';
export * from './BlocklistIdentifierApi';
export * from './ClientApi';
export * from './DomainApi';
export * from './EmailAddressApi';
export * from './IdPOAuthAccessTokenApi';
export * from './InstanceApi';
export * from './InvitationApi';
export * from './MachineTokensApi';
export * from './JwksApi';
export * from './JwtTemplatesApi';
export * from './OrganizationApi';
Expand Down
6 changes: 6 additions & 0 deletions packages/backend/src/api/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ import {
AccountlessApplicationAPI,
ActorTokenAPI,
AllowlistIdentifierAPI,
APIKeysAPI,
BetaFeaturesAPI,
BlocklistIdentifierAPI,
ClientAPI,
DomainAPI,
EmailAddressAPI,
IdPOAuthAccessTokenApi,
InstanceAPI,
InvitationAPI,
JwksAPI,
JwtTemplatesApi,
MachineTokensApi,
OAuthApplicationsApi,
OrganizationAPI,
PhoneNumberAPI,
Expand Down Expand Up @@ -47,6 +50,9 @@ export function createBackendApiClient(options: CreateBackendApiOptions) {
emailAddresses: new EmailAddressAPI(request),
instance: new InstanceAPI(request),
invitations: new InvitationAPI(request),
machineTokens: new MachineTokensApi(request),
idPOAuthAccessToken: new IdPOAuthAccessTokenApi(request),
apiKeys: new APIKeysAPI(request),
jwks: new JwksAPI(request),
jwtTemplates: new JwtTemplatesApi(request),
oauthApplications: new OAuthApplicationsApi(request),
Expand Down
31 changes: 31 additions & 0 deletions packages/backend/src/api/resources/APIKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { APIKeyJSON } from './JSON';

export class APIKey {
constructor(
readonly id: string,
readonly type: string,
readonly name: string,
readonly subject: string,
readonly claims: Record<string, string> | null,
readonly createdBy: string | null,
readonly creationReason: string | null,
readonly secondsUntilExpiration: number | null,
readonly createdAt: number,
readonly expiresAt: number | null,
) {}

static fromJSON(data: APIKeyJSON) {
return new APIKey(
data.id,
data.type,
data.name,
data.subject,
data.claims,
data.created_by,
data.creation_reason,
data.seconds_until_expiration,
data.created_at,
data.expires_at,
);
}
}
9 changes: 9 additions & 0 deletions packages/backend/src/api/resources/Deserializer.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import {
ActorToken,
AllowlistIdentifier,
APIKey,
BlocklistIdentifier,
Client,
Cookies,
DeletedObject,
Domain,
Email,
EmailAddress,
IdPOAuthAccessToken,
Instance,
InstanceRestrictions,
InstanceSettings,
Invitation,
JwtTemplate,
MachineToken,
OauthAccessToken,
OAuthApplication,
Organization,
Expand Down Expand Up @@ -85,6 +88,8 @@ function jsonToObject(item: any): any {
return ActorToken.fromJSON(item);
case ObjectType.AllowlistIdentifier:
return AllowlistIdentifier.fromJSON(item);
case ObjectType.ApiKey:
return APIKey.fromJSON(item);
case ObjectType.BlocklistIdentifier:
return BlocklistIdentifier.fromJSON(item);
case ObjectType.Client:
Expand All @@ -97,6 +102,8 @@ function jsonToObject(item: any): any {
return EmailAddress.fromJSON(item);
case ObjectType.Email:
return Email.fromJSON(item);
case ObjectType.IdpOAuthAccessToken:
return IdPOAuthAccessToken.fromJSON(item);
case ObjectType.Instance:
return Instance.fromJSON(item);
case ObjectType.InstanceRestrictions:
Expand All @@ -107,6 +114,8 @@ function jsonToObject(item: any): any {
return Invitation.fromJSON(item);
case ObjectType.JwtTemplate:
return JwtTemplate.fromJSON(item);
case ObjectType.MachineToken:
return MachineToken.fromJSON(item);
case ObjectType.OauthAccessToken:
return OauthAccessToken.fromJSON(item);
case ObjectType.OAuthApplication:
Expand Down
25 changes: 25 additions & 0 deletions packages/backend/src/api/resources/IdPOAuthAccessToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { IdPOAuthAccessTokenJSON } from './JSON';

export class IdPOAuthAccessToken {
constructor(
readonly id: string,
readonly type: string,
readonly name: string,
readonly subject: string,
readonly claims: Record<string, string> | null,
readonly createdAt: number,
readonly expiresAt: number,
) {}

static fromJSON(data: IdPOAuthAccessTokenJSON) {
return new IdPOAuthAccessToken(
data.id,
data.type,
data.name,
data.subject,
data.claims,
data.created_at,
data.expires_at,
);
}
}
40 changes: 40 additions & 0 deletions packages/backend/src/api/resources/JSON.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const ObjectType = {
AccountlessApplication: 'accountless_application',
ActorToken: 'actor_token',
AllowlistIdentifier: 'allowlist_identifier',
ApiKey: 'api_key',
BlocklistIdentifier: 'blocklist_identifier',
Client: 'client',
Cookies: 'cookies',
Expand All @@ -33,8 +34,10 @@ export const ObjectType = {
InstanceRestrictions: 'instance_restrictions',
InstanceSettings: 'instance_settings',
Invitation: 'invitation',
MachineToken: 'machine_token',
JwtTemplate: 'jwt_template',
OauthAccessToken: 'oauth_access_token',
IdpOAuthAccessToken: 'clerk_idp_oauth_access_token',
OAuthApplication: 'oauth_application',
Organization: 'organization',
OrganizationDomain: 'organization_domain',
Expand Down Expand Up @@ -672,6 +675,43 @@ export interface SamlAccountConnectionJSON extends ClerkResourceJSON {
updated_at: number;
}

export interface MachineTokenJSON extends ClerkResourceJSON {
object: typeof ObjectType.MachineToken;
name: string;
subject: string;
claims: Record<string, string> | null;
revoked: boolean;
expired: boolean;
expiration: number | null;
created_by: string | null;
creation_reason: string | null;
created_at: number;
updated_at: number;
}

export interface APIKeyJSON extends ClerkResourceJSON {
object: typeof ObjectType.ApiKey;
type: string;
name: string;
subject: string;
claims: Record<string, string> | null;
created_by: string | null;
creation_reason: string | null;
seconds_until_expiration: number | null;
created_at: number;
expires_at: number | null;
}

export interface IdPOAuthAccessTokenJSON extends ClerkResourceJSON {
object: typeof ObjectType.IdpOAuthAccessToken;
type: string;
name: string;
subject: string;
claims: Record<string, string> | null;
created_at: number;
expires_at: number;
}

export interface WebhooksSvixJSON {
svix_url: string;
}
33 changes: 33 additions & 0 deletions packages/backend/src/api/resources/MachineToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { MachineTokenJSON } from './JSON';

export class MachineToken {
constructor(
readonly id: string,
readonly name: string,
readonly subject: string,
readonly claims: Record<string, string> | null,
readonly revoked: boolean,
readonly expired: boolean,
readonly expiration: number | null,
readonly createdBy: string | null,
readonly creationReason: string | null,
readonly createdAt: number,
readonly updatedAt: number,
) {}

static fromJSON(data: MachineTokenJSON) {
return new MachineToken(
data.id,
data.name,
data.subject,
data.claims,
data.revoked,
data.expired,
data.expiration,
data.created_by,
data.creation_reason,
data.created_at,
data.updated_at,
);
}
}
3 changes: 3 additions & 0 deletions packages/backend/src/api/resources/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './ActorToken';
export * from './AccountlessApplication';
export * from './AllowlistIdentifier';
export * from './APIKey';
export * from './BlocklistIdentifier';
export * from './Client';
export * from './CnameTarget';
Expand All @@ -23,11 +24,13 @@ export type { SignUpStatus } from '@clerk/types';

export * from './ExternalAccount';
export * from './IdentificationLink';
export * from './IdPOAuthAccessToken';
export * from './Instance';
export * from './InstanceRestrictions';
export * from './InstanceSettings';
export * from './Invitation';
export * from './JSON';
export * from './MachineToken';
export * from './JwtTemplate';
export * from './OauthAccessToken';
export * from './OAuthApplication';
Expand Down
27 changes: 27 additions & 0 deletions packages/backend/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,30 @@ export class TokenVerificationError extends Error {
}

export class SignJWTError extends Error {}

export const MachineTokenVerificationErrorCode = {
TokenInvalid: 'token-invalid',
InvalidSecretKey: 'secret-key-invalid',
UnexpectedError: 'unexpected-error',
} as const;

export type MachineTokenVerificationErrorCode =
(typeof MachineTokenVerificationErrorCode)[keyof typeof MachineTokenVerificationErrorCode];

export class MachineTokenVerificationError extends Error {
code: MachineTokenVerificationErrorCode;
long_message?: string;
status: number;

constructor({ message, code, status }: { message: string; code: MachineTokenVerificationErrorCode; status: number }) {
super(message);
Object.setPrototypeOf(this, MachineTokenVerificationError.prototype);

this.code = code;
this.status = status;
}

public getFullMessage() {
return `${this.message} (code=${this.code}, status=${this.status})`;
}
}
Loading
Loading