@rudderjs/passport 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/grants/authorization-code.d.ts +56 -0
  2. package/dist/grants/authorization-code.d.ts.map +1 -0
  3. package/dist/grants/authorization-code.js +152 -0
  4. package/dist/grants/authorization-code.js.map +1 -0
  5. package/dist/grants/client-credentials.d.ts +13 -0
  6. package/dist/grants/client-credentials.d.ts.map +1 -0
  7. package/dist/grants/client-credentials.js +37 -0
  8. package/dist/grants/client-credentials.js.map +1 -0
  9. package/dist/grants/device-code.d.ts +43 -0
  10. package/dist/grants/device-code.d.ts.map +1 -0
  11. package/dist/grants/device-code.js +120 -0
  12. package/dist/grants/device-code.js.map +1 -0
  13. package/dist/grants/index.d.ts +11 -0
  14. package/dist/grants/index.d.ts.map +1 -0
  15. package/dist/grants/index.js +6 -0
  16. package/dist/grants/index.js.map +1 -0
  17. package/dist/grants/issue-tokens.d.ts +18 -0
  18. package/dist/grants/issue-tokens.d.ts.map +1 -0
  19. package/dist/grants/issue-tokens.js +45 -0
  20. package/dist/grants/issue-tokens.js.map +1 -0
  21. package/dist/grants/refresh-token.d.ts +14 -0
  22. package/dist/grants/refresh-token.d.ts.map +1 -0
  23. package/dist/grants/refresh-token.js +72 -0
  24. package/dist/grants/refresh-token.js.map +1 -0
  25. package/dist/index.d.ts +6 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +10 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/models/AccessToken.d.ts.map +1 -1
  30. package/dist/models/AccessToken.js +1 -1
  31. package/dist/models/AccessToken.js.map +1 -1
  32. package/dist/models/AuthCode.d.ts.map +1 -1
  33. package/dist/models/AuthCode.js +1 -1
  34. package/dist/models/AuthCode.js.map +1 -1
  35. package/dist/models/DeviceCode.d.ts.map +1 -1
  36. package/dist/models/DeviceCode.js +1 -1
  37. package/dist/models/DeviceCode.js.map +1 -1
  38. package/dist/models/OAuthClient.d.ts.map +1 -1
  39. package/dist/models/OAuthClient.js +1 -1
  40. package/dist/models/OAuthClient.js.map +1 -1
  41. package/dist/models/RefreshToken.d.ts.map +1 -1
  42. package/dist/models/RefreshToken.js +1 -1
  43. package/dist/models/RefreshToken.js.map +1 -1
  44. package/dist/models/helpers.d.ts +77 -0
  45. package/dist/models/helpers.d.ts.map +1 -0
  46. package/dist/models/helpers.js +53 -0
  47. package/dist/models/helpers.js.map +1 -0
  48. package/dist/personal-access-tokens.d.ts +42 -0
  49. package/dist/personal-access-tokens.d.ts.map +1 -0
  50. package/dist/personal-access-tokens.js +106 -0
  51. package/dist/personal-access-tokens.js.map +1 -0
  52. package/dist/routes.d.ts +22 -0
  53. package/dist/routes.d.ts.map +1 -0
  54. package/dist/routes.js +215 -0
  55. package/dist/routes.js.map +1 -0
  56. package/package.json +9 -5
@@ -0,0 +1,56 @@
1
+ import { OAuthClient } from '../models/OAuthClient.js';
2
+ import { type IssuedTokens } from './issue-tokens.js';
3
+ export interface AuthorizationRequest {
4
+ clientId: string;
5
+ redirectUri: string;
6
+ responseType: string;
7
+ scope: string;
8
+ state?: string;
9
+ codeChallenge?: string;
10
+ codeChallengeMethod?: string;
11
+ }
12
+ export interface ValidatedAuthRequest {
13
+ client: OAuthClient;
14
+ redirectUri: string;
15
+ scopes: string[];
16
+ state?: string;
17
+ codeChallenge?: string;
18
+ codeChallengeMethod?: string;
19
+ }
20
+ /**
21
+ * Validate an authorization request (GET /oauth/authorize).
22
+ * Returns the validated request or throws with an error message.
23
+ */
24
+ export declare function validateAuthorizationRequest(params: AuthorizationRequest): Promise<ValidatedAuthRequest>;
25
+ /**
26
+ * Create an authorization code after user approval.
27
+ * The code is short-lived (10 minutes) and single-use.
28
+ */
29
+ export declare function issueAuthCode(opts: {
30
+ userId: string;
31
+ clientId: string;
32
+ scopes: string[];
33
+ redirectUri: string;
34
+ codeChallenge?: string;
35
+ codeChallengeMethod?: string;
36
+ }): Promise<string>;
37
+ export interface TokenExchangeRequest {
38
+ grantType: string;
39
+ code: string;
40
+ clientId: string;
41
+ clientSecret?: string;
42
+ redirectUri: string;
43
+ codeVerifier?: string;
44
+ }
45
+ /**
46
+ * Exchange an authorization code for access + refresh tokens.
47
+ */
48
+ export declare function exchangeAuthCode(params: TokenExchangeRequest): Promise<IssuedTokens>;
49
+ export declare class OAuthError extends Error {
50
+ readonly error: string;
51
+ readonly errorDescription: string;
52
+ readonly statusCode: number;
53
+ constructor(error: string, errorDescription: string, statusCode?: number);
54
+ toJSON(): Record<string, string>;
55
+ }
56
+ //# sourceMappingURL=authorization-code.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorization-code.d.ts","sourceRoot":"","sources":["../../src/grants/authorization-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAGtD,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAIlE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAO,MAAM,CAAA;IACrB,WAAW,EAAI,MAAM,CAAA;IACrB,YAAY,EAAG,MAAM,CAAA;IACrB,KAAK,EAAU,MAAM,CAAA;IACrB,KAAK,CAAC,EAAS,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAS,WAAW,CAAA;IAC1B,WAAW,EAAI,MAAM,CAAA;IACrB,MAAM,EAAS,MAAM,EAAE,CAAA;IACvB,KAAK,CAAC,EAAS,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAyC9G;AAID;;;GAGG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,MAAM,EAAK,MAAM,CAAA;IACjB,QAAQ,EAAG,MAAM,CAAA;IACjB,MAAM,EAAK,MAAM,EAAE,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B,GAAG,OAAO,CAAC,MAAM,CAAC,CAclB;AAID,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAK,MAAM,CAAA;IACpB,IAAI,EAAU,MAAM,CAAA;IACpB,QAAQ,EAAM,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,EAAG,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,CAAC,CAuE1F;AAID,qBAAa,UAAW,SAAQ,KAAK;aAEjB,KAAK,EAAE,MAAM;aACb,gBAAgB,EAAE,MAAM;aACxB,UAAU,EAAE,MAAM;gBAFlB,KAAK,EAAE,MAAM,EACb,gBAAgB,EAAE,MAAM,EACxB,UAAU,GAAE,MAAY;IAM1C,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CAMjC"}
@@ -0,0 +1,152 @@
1
+ import { OAuthClient } from '../models/OAuthClient.js';
2
+ import { AuthCode } from '../models/AuthCode.js';
3
+ import { clientHelpers, authCodeHelpers } from '../models/helpers.js';
4
+ import { issueTokens } from './issue-tokens.js';
5
+ /**
6
+ * Validate an authorization request (GET /oauth/authorize).
7
+ * Returns the validated request or throws with an error message.
8
+ */
9
+ export async function validateAuthorizationRequest(params) {
10
+ if (params.responseType !== 'code') {
11
+ throw new OAuthError('unsupported_response_type', 'Only response_type=code is supported.');
12
+ }
13
+ const client = await OAuthClient.where('id', params.clientId).first();
14
+ if (!client || client.revoked) {
15
+ throw new OAuthError('invalid_client', 'Client not found.');
16
+ }
17
+ if (!clientHelpers.hasGrantType(client, 'authorization_code')) {
18
+ throw new OAuthError('unauthorized_client', 'Client is not authorized for authorization_code grant.');
19
+ }
20
+ if (!clientHelpers.hasRedirectUri(client, params.redirectUri)) {
21
+ throw new OAuthError('invalid_request', 'Invalid redirect_uri.');
22
+ }
23
+ // PKCE validation
24
+ if (params.codeChallenge) {
25
+ if (params.codeChallengeMethod && params.codeChallengeMethod !== 'S256' && params.codeChallengeMethod !== 'plain') {
26
+ throw new OAuthError('invalid_request', 'Unsupported code_challenge_method. Use S256 or plain.');
27
+ }
28
+ }
29
+ else if (clientHelpers.isPublic(client)) {
30
+ // Public clients MUST use PKCE
31
+ throw new OAuthError('invalid_request', 'Public clients must use PKCE (code_challenge required).');
32
+ }
33
+ const scopes = params.scope ? params.scope.split(' ').filter(Boolean) : [];
34
+ const result = {
35
+ client,
36
+ redirectUri: params.redirectUri,
37
+ scopes,
38
+ };
39
+ if (params.state !== undefined)
40
+ result.state = params.state;
41
+ if (params.codeChallenge !== undefined)
42
+ result.codeChallenge = params.codeChallenge;
43
+ const method = params.codeChallengeMethod ?? (params.codeChallenge ? 'S256' : undefined);
44
+ if (method !== undefined)
45
+ result.codeChallengeMethod = method;
46
+ return result;
47
+ }
48
+ // ─── Issue Authorization Code ─────────────────────────────
49
+ /**
50
+ * Create an authorization code after user approval.
51
+ * The code is short-lived (10 minutes) and single-use.
52
+ */
53
+ export async function issueAuthCode(opts) {
54
+ const expiresAt = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes
55
+ const code = await AuthCode.create({
56
+ userId: opts.userId,
57
+ clientId: opts.clientId,
58
+ scopes: JSON.stringify(opts.scopes),
59
+ revoked: false,
60
+ expiresAt,
61
+ codeChallenge: opts.codeChallenge ?? null,
62
+ codeChallengeMethod: opts.codeChallengeMethod ?? null,
63
+ });
64
+ return code.id;
65
+ }
66
+ /**
67
+ * Exchange an authorization code for access + refresh tokens.
68
+ */
69
+ export async function exchangeAuthCode(params) {
70
+ if (params.grantType !== 'authorization_code') {
71
+ throw new OAuthError('unsupported_grant_type', 'Expected grant_type=authorization_code.');
72
+ }
73
+ // Validate client
74
+ const client = await OAuthClient.where('id', params.clientId).first();
75
+ if (!client || client.revoked) {
76
+ throw new OAuthError('invalid_client', 'Client not found.');
77
+ }
78
+ // Confidential clients must provide a valid secret
79
+ if (client.confidential) {
80
+ if (!params.clientSecret) {
81
+ throw new OAuthError('invalid_client', 'Client secret required.');
82
+ }
83
+ const { createHash } = await import('node:crypto');
84
+ const hashed = createHash('sha256').update(params.clientSecret).digest('hex');
85
+ if (hashed !== client.secret) {
86
+ throw new OAuthError('invalid_client', 'Invalid client secret.');
87
+ }
88
+ }
89
+ // Validate auth code
90
+ const authCode = await AuthCode.where('id', params.code).first();
91
+ if (!authCode) {
92
+ throw new OAuthError('invalid_grant', 'Authorization code not found.');
93
+ }
94
+ if (authCode.revoked) {
95
+ throw new OAuthError('invalid_grant', 'Authorization code has been revoked.');
96
+ }
97
+ if (authCodeHelpers.isExpired(authCode)) {
98
+ throw new OAuthError('invalid_grant', 'Authorization code has expired.');
99
+ }
100
+ if (authCode.clientId !== params.clientId) {
101
+ throw new OAuthError('invalid_grant', 'Authorization code was not issued to this client.');
102
+ }
103
+ // PKCE verification
104
+ if (authCode.codeChallenge) {
105
+ if (!params.codeVerifier) {
106
+ throw new OAuthError('invalid_grant', 'PKCE code_verifier required.');
107
+ }
108
+ const { createHash } = await import('node:crypto');
109
+ let expected;
110
+ if (authCode.codeChallengeMethod === 'S256') {
111
+ expected = createHash('sha256')
112
+ .update(params.codeVerifier)
113
+ .digest('base64url');
114
+ }
115
+ else {
116
+ // plain
117
+ expected = params.codeVerifier;
118
+ }
119
+ if (expected !== authCode.codeChallenge) {
120
+ throw new OAuthError('invalid_grant', 'PKCE code_verifier does not match.');
121
+ }
122
+ }
123
+ // Revoke the auth code (single-use)
124
+ await AuthCode.update(authCode.id, { revoked: true });
125
+ // Issue tokens
126
+ return issueTokens({
127
+ userId: authCode.userId,
128
+ clientId: params.clientId,
129
+ scopes: authCodeHelpers.getScopes(authCode),
130
+ includeRefresh: true,
131
+ });
132
+ }
133
+ // ─── OAuth Error ──────────────────────────────────────────
134
+ export class OAuthError extends Error {
135
+ error;
136
+ errorDescription;
137
+ statusCode;
138
+ constructor(error, errorDescription, statusCode = 400) {
139
+ super(errorDescription);
140
+ this.error = error;
141
+ this.errorDescription = errorDescription;
142
+ this.statusCode = statusCode;
143
+ this.name = 'OAuthError';
144
+ }
145
+ toJSON() {
146
+ return {
147
+ error: this.error,
148
+ error_description: this.errorDescription,
149
+ };
150
+ }
151
+ }
152
+ //# sourceMappingURL=authorization-code.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorization-code.js","sourceRoot":"","sources":["../../src/grants/authorization-code.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { type IssuedTokens } from './issue-tokens.js';
2
+ export interface ClientCredentialsRequest {
3
+ grantType: string;
4
+ clientId: string;
5
+ clientSecret: string;
6
+ scope?: string;
7
+ }
8
+ /**
9
+ * Client credentials grant — machine-to-machine, no user context.
10
+ * Issues an access token (no refresh token).
11
+ */
12
+ export declare function clientCredentialsGrant(params: ClientCredentialsRequest): Promise<IssuedTokens>;
13
+ //# sourceMappingURL=client-credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-credentials.d.ts","sourceRoot":"","sources":["../../src/grants/client-credentials.ts"],"names":[],"mappings":"AAEA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGlE,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAK,MAAM,CAAA;IACpB,QAAQ,EAAM,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAQ,MAAM,CAAA;CACrB;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,YAAY,CAAC,CAiCpG"}
@@ -0,0 +1,37 @@
1
+ import { OAuthClient } from '../models/OAuthClient.js';
2
+ import { clientHelpers } from '../models/helpers.js';
3
+ import { issueTokens } from './issue-tokens.js';
4
+ import { OAuthError } from './authorization-code.js';
5
+ /**
6
+ * Client credentials grant — machine-to-machine, no user context.
7
+ * Issues an access token (no refresh token).
8
+ */
9
+ export async function clientCredentialsGrant(params) {
10
+ if (params.grantType !== 'client_credentials') {
11
+ throw new OAuthError('unsupported_grant_type', 'Expected grant_type=client_credentials.');
12
+ }
13
+ const client = await OAuthClient.where('id', params.clientId).first();
14
+ if (!client || client.revoked) {
15
+ throw new OAuthError('invalid_client', 'Client not found.', 401);
16
+ }
17
+ if (!clientHelpers.hasGrantType(client, 'client_credentials')) {
18
+ throw new OAuthError('unauthorized_client', 'Client is not authorized for client_credentials grant.');
19
+ }
20
+ if (!client.confidential) {
21
+ throw new OAuthError('invalid_client', 'Client credentials grant requires a confidential client.');
22
+ }
23
+ // Verify secret
24
+ const { createHash } = await import('node:crypto');
25
+ const hashed = createHash('sha256').update(params.clientSecret).digest('hex');
26
+ if (hashed !== client.secret) {
27
+ throw new OAuthError('invalid_client', 'Invalid client secret.', 401);
28
+ }
29
+ const scopes = params.scope ? params.scope.split(' ').filter(Boolean) : [];
30
+ return issueTokens({
31
+ userId: null, // no user context
32
+ clientId: params.clientId,
33
+ scopes,
34
+ includeRefresh: false, // client credentials don't get refresh tokens
35
+ });
36
+ }
37
+ //# sourceMappingURL=client-credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-credentials.js","sourceRoot":"","sources":["../../src/grants/client-credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,WAAW,EAAqB,MAAM,mBAAmB,CAAA;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAA;AASpD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,MAAgC;IAC3E,IAAI,MAAM,CAAC,SAAS,KAAK,oBAAoB,EAAE,CAAC;QAC9C,MAAM,IAAI,UAAU,CAAC,wBAAwB,EAAE,yCAAyC,CAAC,CAAA;IAC3F,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAwB,CAAA;IAC3F,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QAC9B,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,mBAAmB,EAAE,GAAG,CAAC,CAAA;IAClE,CAAC;IAED,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,MAAa,EAAE,oBAAoB,CAAC,EAAE,CAAC;QACrE,MAAM,IAAI,UAAU,CAAC,qBAAqB,EAAE,wDAAwD,CAAC,CAAA;IACvG,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;QACzB,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,0DAA0D,CAAC,CAAA;IACpG,CAAC;IAED,gBAAgB;IAChB,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAA;IAClD,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAC7E,IAAI,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC;QAC7B,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,wBAAwB,EAAE,GAAG,CAAC,CAAA;IACvE,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAE1E,OAAO,WAAW,CAAC;QACjB,MAAM,EAAU,IAAI,EAAE,kBAAkB;QACxC,QAAQ,EAAQ,MAAM,CAAC,QAAQ;QAC/B,MAAM;QACN,cAAc,EAAE,KAAK,EAAE,8CAA8C;KACtE,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,43 @@
1
+ import { type IssuedTokens } from './issue-tokens.js';
2
+ export interface DeviceAuthorizationResponse {
3
+ device_code: string;
4
+ user_code: string;
5
+ verification_uri: string;
6
+ verification_uri_complete?: string;
7
+ expires_in: number;
8
+ interval: number;
9
+ }
10
+ /**
11
+ * Step 1: Device requests authorization codes.
12
+ * Returns device_code + user_code for the user to enter.
13
+ */
14
+ export declare function requestDeviceCode(params: {
15
+ clientId: string;
16
+ scope?: string;
17
+ verificationUri: string;
18
+ }): Promise<DeviceAuthorizationResponse>;
19
+ /**
20
+ * Step 2: User approves or denies the device (on the verification page).
21
+ */
22
+ export declare function approveDeviceCode(userCode: string, userId: string, approved: boolean): Promise<void>;
23
+ export type DevicePollResult = {
24
+ status: 'authorized';
25
+ tokens: IssuedTokens;
26
+ } | {
27
+ status: 'authorization_pending';
28
+ } | {
29
+ status: 'slow_down';
30
+ } | {
31
+ status: 'access_denied';
32
+ } | {
33
+ status: 'expired_token';
34
+ };
35
+ /**
36
+ * Step 3: Device polls for tokens using the device_code.
37
+ */
38
+ export declare function pollDeviceCode(params: {
39
+ grantType: string;
40
+ deviceCode: string;
41
+ clientId: string;
42
+ }): Promise<DevicePollResult>;
43
+ //# sourceMappingURL=device-code.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-code.d.ts","sourceRoot":"","sources":["../../src/grants/device-code.ts"],"names":[],"mappings":"AAGA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAKlE,MAAM,WAAW,2BAA2B;IAC1C,WAAW,EAAiB,MAAM,CAAA;IAClC,SAAS,EAAmB,MAAM,CAAA;IAClC,gBAAgB,EAAY,MAAM,CAAA;IAClC,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,UAAU,EAAkB,MAAM,CAAA;IAClC,QAAQ,EAAoB,MAAM,CAAA;CACnC;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE;IAC9C,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAI,MAAM,CAAA;IAChB,eAAe,EAAE,MAAM,CAAA;CACxB,GAAG,OAAO,CAAC,2BAA2B,CAAC,CAmCvC;AAID;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB1G;AAID,MAAM,MAAM,gBAAgB,GACxB;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GAC9C;IAAE,MAAM,EAAE,uBAAuB,CAAA;CAAE,GACnC;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,GACvB;IAAE,MAAM,EAAE,eAAe,CAAA;CAAE,GAC3B;IAAE,MAAM,EAAE,eAAe,CAAA;CAAE,CAAA;AAE/B;;GAEG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE;IAC3C,SAAS,EAAG,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAI,MAAM,CAAA;CACnB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiD5B"}
@@ -0,0 +1,120 @@
1
+ import { OAuthClient } from '../models/OAuthClient.js';
2
+ import { DeviceCode } from '../models/DeviceCode.js';
3
+ import { clientHelpers, deviceCodeHelpers } from '../models/helpers.js';
4
+ import { issueTokens } from './issue-tokens.js';
5
+ import { OAuthError } from './authorization-code.js';
6
+ /**
7
+ * Step 1: Device requests authorization codes.
8
+ * Returns device_code + user_code for the user to enter.
9
+ */
10
+ export async function requestDeviceCode(params) {
11
+ const client = await OAuthClient.where('id', params.clientId).first();
12
+ if (!client || client.revoked) {
13
+ throw new OAuthError('invalid_client', 'Client not found.');
14
+ }
15
+ if (!clientHelpers.hasGrantType(client, 'urn:ietf:params:oauth:grant-type:device_code')) {
16
+ throw new OAuthError('unauthorized_client', 'Client is not authorized for device authorization grant.');
17
+ }
18
+ const { randomBytes } = await import('node:crypto');
19
+ const deviceCode = randomBytes(32).toString('hex');
20
+ const userCode = await generateUserCode();
21
+ const scopes = params.scope ? params.scope.split(' ').filter(Boolean) : [];
22
+ const expiresAt = new Date(Date.now() + 15 * 60 * 1000); // 15 minutes
23
+ await DeviceCode.create({
24
+ clientId: params.clientId,
25
+ deviceCode,
26
+ userCode,
27
+ scopes: JSON.stringify(scopes),
28
+ userId: null,
29
+ approved: null,
30
+ expiresAt,
31
+ lastPolledAt: null,
32
+ });
33
+ return {
34
+ device_code: deviceCode,
35
+ user_code: userCode,
36
+ verification_uri: params.verificationUri,
37
+ verification_uri_complete: `${params.verificationUri}?user_code=${userCode}`,
38
+ expires_in: 15 * 60, // 15 minutes in seconds
39
+ interval: 5, // poll every 5 seconds
40
+ };
41
+ }
42
+ // ─── User Approval ────────────────────────────────────────
43
+ /**
44
+ * Step 2: User approves or denies the device (on the verification page).
45
+ */
46
+ export async function approveDeviceCode(userCode, userId, approved) {
47
+ const device = await DeviceCode.where('userCode', userCode).first();
48
+ if (!device) {
49
+ throw new OAuthError('invalid_request', 'Device code not found.');
50
+ }
51
+ if (deviceCodeHelpers.isExpired(device)) {
52
+ throw new OAuthError('expired_token', 'Device code has expired.');
53
+ }
54
+ if (!deviceCodeHelpers.isPending(device)) {
55
+ throw new OAuthError('invalid_request', 'Device code has already been used.');
56
+ }
57
+ await DeviceCode.update(device.id, {
58
+ userId,
59
+ approved,
60
+ });
61
+ }
62
+ /**
63
+ * Step 3: Device polls for tokens using the device_code.
64
+ */
65
+ export async function pollDeviceCode(params) {
66
+ if (params.grantType !== 'urn:ietf:params:oauth:grant-type:device_code') {
67
+ throw new OAuthError('unsupported_grant_type', 'Expected grant_type=urn:ietf:params:oauth:grant-type:device_code.');
68
+ }
69
+ const device = await DeviceCode.where('deviceCode', params.deviceCode).first();
70
+ if (!device) {
71
+ throw new OAuthError('invalid_grant', 'Device code not found.');
72
+ }
73
+ if (device.clientId !== params.clientId) {
74
+ throw new OAuthError('invalid_grant', 'Device code was not issued to this client.');
75
+ }
76
+ if (deviceCodeHelpers.isExpired(device)) {
77
+ return { status: 'expired_token' };
78
+ }
79
+ // Rate limiting: enforce 5-second interval
80
+ if (device.lastPolledAt) {
81
+ const elapsed = Date.now() - new Date(device.lastPolledAt).getTime();
82
+ if (elapsed < 5000) {
83
+ return { status: 'slow_down' };
84
+ }
85
+ }
86
+ // Update last polled time
87
+ await DeviceCode.update(device.id, {
88
+ lastPolledAt: new Date(),
89
+ });
90
+ if (deviceCodeHelpers.isPending(device)) {
91
+ return { status: 'authorization_pending' };
92
+ }
93
+ if (deviceCodeHelpers.isDenied(device)) {
94
+ return { status: 'access_denied' };
95
+ }
96
+ // Approved — issue tokens
97
+ const tokens = await issueTokens({
98
+ userId: device.userId,
99
+ clientId: params.clientId,
100
+ scopes: deviceCodeHelpers.getScopes(device),
101
+ includeRefresh: true,
102
+ });
103
+ // Clean up the device code
104
+ await DeviceCode.delete(device.id);
105
+ return { status: 'authorized', tokens };
106
+ }
107
+ // ─── Helpers ──────────────────────────────────────────────
108
+ /** Generate a human-readable user code (8 chars, uppercase, no ambiguous chars). */
109
+ async function generateUserCode() {
110
+ const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // no I, O, 0, 1
111
+ const { randomInt } = await import('node:crypto');
112
+ let code = '';
113
+ for (let i = 0; i < 8; i++) {
114
+ if (i === 4)
115
+ code += '-'; // XXXX-XXXX format
116
+ code += chars[randomInt(chars.length)];
117
+ }
118
+ return code;
119
+ }
120
+ //# sourceMappingURL=device-code.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-code.js","sourceRoot":"","sources":["../../src/grants/device-code.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export { issueTokens } from './issue-tokens.js';
2
+ export type { IssuedTokens } from './issue-tokens.js';
3
+ export { validateAuthorizationRequest, issueAuthCode, exchangeAuthCode, OAuthError, } from './authorization-code.js';
4
+ export type { AuthorizationRequest, ValidatedAuthRequest, TokenExchangeRequest, } from './authorization-code.js';
5
+ export { clientCredentialsGrant } from './client-credentials.js';
6
+ export type { ClientCredentialsRequest } from './client-credentials.js';
7
+ export { refreshTokenGrant } from './refresh-token.js';
8
+ export type { RefreshTokenRequest } from './refresh-token.js';
9
+ export { requestDeviceCode, approveDeviceCode, pollDeviceCode, } from './device-code.js';
10
+ export type { DeviceAuthorizationResponse, DevicePollResult, } from './device-code.js';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/grants/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD,OAAO,EACL,4BAA4B,EAC5B,aAAa,EACb,gBAAgB,EAChB,UAAU,GACX,MAAM,yBAAyB,CAAA;AAChC,YAAY,EACV,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,yBAAyB,CAAA;AAEhC,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAChE,YAAY,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAA;AAEvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACtD,YAAY,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAE7D,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,GACf,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,2BAA2B,EAC3B,gBAAgB,GACjB,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,6 @@
1
+ export { issueTokens } from './issue-tokens.js';
2
+ export { validateAuthorizationRequest, issueAuthCode, exchangeAuthCode, OAuthError, } from './authorization-code.js';
3
+ export { clientCredentialsGrant } from './client-credentials.js';
4
+ export { refreshTokenGrant } from './refresh-token.js';
5
+ export { requestDeviceCode, approveDeviceCode, pollDeviceCode, } from './device-code.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/grants/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAG/C,OAAO,EACL,4BAA4B,EAC5B,aAAa,EACb,gBAAgB,EAChB,UAAU,GACX,MAAM,yBAAyB,CAAA;AAOhC,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAA;AAGhE,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAGtD,OAAO,EACL,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,GACf,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,18 @@
1
+ export interface IssuedTokens {
2
+ access_token: string;
3
+ token_type: 'Bearer';
4
+ expires_in: number;
5
+ refresh_token?: string;
6
+ }
7
+ /**
8
+ * Issue an access token (+ optional refresh token) and persist to DB.
9
+ */
10
+ export declare function issueTokens(opts: {
11
+ userId: string | null;
12
+ clientId: string;
13
+ scopes: string[];
14
+ includeRefresh?: boolean;
15
+ /** Override access token lifetime in ms */
16
+ lifetime?: number;
17
+ }): Promise<IssuedTokens>;
18
+ //# sourceMappingURL=issue-tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue-tokens.d.ts","sourceRoot":"","sources":["../../src/grants/issue-tokens.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAG,MAAM,CAAA;IACrB,UAAU,EAAK,QAAQ,CAAA;IACvB,UAAU,EAAK,MAAM,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE;IACtC,MAAM,EAAQ,MAAM,GAAG,IAAI,CAAA;IAC3B,QAAQ,EAAM,MAAM,CAAA;IACpB,MAAM,EAAQ,MAAM,EAAE,CAAA;IACtB,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,2CAA2C;IAC3C,QAAQ,CAAC,EAAK,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC,YAAY,CAAC,CA2CxB"}
@@ -0,0 +1,45 @@
1
+ import { Passport } from '../Passport.js';
2
+ import { AccessToken } from '../models/AccessToken.js';
3
+ import { RefreshToken } from '../models/RefreshToken.js';
4
+ import { createToken } from '../token.js';
5
+ /**
6
+ * Issue an access token (+ optional refresh token) and persist to DB.
7
+ */
8
+ export async function issueTokens(opts) {
9
+ const lifetime = opts.lifetime ?? Passport.tokenLifetime();
10
+ const expiresAt = new Date(Date.now() + lifetime);
11
+ // Create DB record
12
+ const tokenRecord = await AccessToken.create({
13
+ userId: opts.userId,
14
+ clientId: opts.clientId,
15
+ scopes: JSON.stringify(opts.scopes),
16
+ revoked: false,
17
+ expiresAt,
18
+ });
19
+ const tokenId = tokenRecord.id;
20
+ // Sign JWT
21
+ const jwt = await createToken({
22
+ tokenId,
23
+ userId: opts.userId,
24
+ clientId: opts.clientId,
25
+ scopes: opts.scopes,
26
+ expiresAt,
27
+ });
28
+ const result = {
29
+ access_token: jwt,
30
+ token_type: 'Bearer',
31
+ expires_in: Math.floor(lifetime / 1000),
32
+ };
33
+ // Issue refresh token
34
+ if (opts.includeRefresh !== false) {
35
+ const refreshExpiresAt = new Date(Date.now() + Passport.refreshTokenLifetime());
36
+ const refreshRecord = await RefreshToken.create({
37
+ accessTokenId: tokenId,
38
+ revoked: false,
39
+ expiresAt: refreshExpiresAt,
40
+ });
41
+ result.refresh_token = refreshRecord.id;
42
+ }
43
+ return result;
44
+ }
45
+ //# sourceMappingURL=issue-tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"issue-tokens.js","sourceRoot":"","sources":["../../src/grants/issue-tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AASzC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAOjC;IACC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAA;IAC1D,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAA;IAEjD,mBAAmB;IACnB,MAAM,WAAW,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC;QAC3C,MAAM,EAAK,IAAI,CAAC,MAAM;QACtB,QAAQ,EAAG,IAAI,CAAC,QAAQ;QACxB,MAAM,EAAK,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC;QACtC,OAAO,EAAI,KAAK;QAChB,SAAS;KACiB,CAAgB,CAAA;IAE5C,MAAM,OAAO,GAAI,WAAmB,CAAC,EAAY,CAAA;IAEjD,WAAW;IACX,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC;QAC5B,OAAO;QACP,MAAM,EAAI,IAAI,CAAC,MAAM;QACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,MAAM,EAAI,IAAI,CAAC,MAAM;QACrB,SAAS;KACV,CAAC,CAAA;IAEF,MAAM,MAAM,GAAiB;QAC3B,YAAY,EAAE,GAAG;QACjB,UAAU,EAAI,QAAQ;QACtB,UAAU,EAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC;KAC1C,CAAA;IAED,sBAAsB;IACtB,IAAI,IAAI,CAAC,cAAc,KAAK,KAAK,EAAE,CAAC;QAClC,MAAM,gBAAgB,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,oBAAoB,EAAE,CAAC,CAAA;QAC/E,MAAM,aAAa,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC;YAC9C,aAAa,EAAE,OAAO;YACtB,OAAO,EAAQ,KAAK;YACpB,SAAS,EAAM,gBAAgB;SACL,CAAiB,CAAA;QAE7C,MAAM,CAAC,aAAa,GAAI,aAAqB,CAAC,EAAY,CAAA;IAC5D,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { type IssuedTokens } from './issue-tokens.js';
2
+ export interface RefreshTokenRequest {
3
+ grantType: string;
4
+ refreshToken: string;
5
+ clientId: string;
6
+ clientSecret?: string;
7
+ scope?: string;
8
+ }
9
+ /**
10
+ * Refresh token grant — exchange a refresh token for a new access + refresh token pair.
11
+ * The old refresh token is revoked.
12
+ */
13
+ export declare function refreshTokenGrant(params: RefreshTokenRequest): Promise<IssuedTokens>;
14
+ //# sourceMappingURL=refresh-token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refresh-token.d.ts","sourceRoot":"","sources":["../../src/grants/refresh-token.ts"],"names":[],"mappings":"AAIA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGlE,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAK,MAAM,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAM,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,EAAQ,MAAM,CAAA;CACrB;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,YAAY,CAAC,CAmE1F"}