@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.
- package/dist/grants/authorization-code.d.ts +56 -0
- package/dist/grants/authorization-code.d.ts.map +1 -0
- package/dist/grants/authorization-code.js +152 -0
- package/dist/grants/authorization-code.js.map +1 -0
- package/dist/grants/client-credentials.d.ts +13 -0
- package/dist/grants/client-credentials.d.ts.map +1 -0
- package/dist/grants/client-credentials.js +37 -0
- package/dist/grants/client-credentials.js.map +1 -0
- package/dist/grants/device-code.d.ts +43 -0
- package/dist/grants/device-code.d.ts.map +1 -0
- package/dist/grants/device-code.js +120 -0
- package/dist/grants/device-code.js.map +1 -0
- package/dist/grants/index.d.ts +11 -0
- package/dist/grants/index.d.ts.map +1 -0
- package/dist/grants/index.js +6 -0
- package/dist/grants/index.js.map +1 -0
- package/dist/grants/issue-tokens.d.ts +18 -0
- package/dist/grants/issue-tokens.d.ts.map +1 -0
- package/dist/grants/issue-tokens.js +45 -0
- package/dist/grants/issue-tokens.js.map +1 -0
- package/dist/grants/refresh-token.d.ts +14 -0
- package/dist/grants/refresh-token.d.ts.map +1 -0
- package/dist/grants/refresh-token.js +72 -0
- package/dist/grants/refresh-token.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/models/AccessToken.d.ts.map +1 -1
- package/dist/models/AccessToken.js +1 -1
- package/dist/models/AccessToken.js.map +1 -1
- package/dist/models/AuthCode.d.ts.map +1 -1
- package/dist/models/AuthCode.js +1 -1
- package/dist/models/AuthCode.js.map +1 -1
- package/dist/models/DeviceCode.d.ts.map +1 -1
- package/dist/models/DeviceCode.js +1 -1
- package/dist/models/DeviceCode.js.map +1 -1
- package/dist/models/OAuthClient.d.ts.map +1 -1
- package/dist/models/OAuthClient.js +1 -1
- package/dist/models/OAuthClient.js.map +1 -1
- package/dist/models/RefreshToken.d.ts.map +1 -1
- package/dist/models/RefreshToken.js +1 -1
- package/dist/models/RefreshToken.js.map +1 -1
- package/dist/models/helpers.d.ts +77 -0
- package/dist/models/helpers.d.ts.map +1 -0
- package/dist/models/helpers.js +53 -0
- package/dist/models/helpers.js.map +1 -0
- package/dist/personal-access-tokens.d.ts +42 -0
- package/dist/personal-access-tokens.d.ts.map +1 -0
- package/dist/personal-access-tokens.js +106 -0
- package/dist/personal-access-tokens.js.map +1 -0
- package/dist/routes.d.ts +22 -0
- package/dist/routes.d.ts.map +1 -0
- package/dist/routes.js +215 -0
- package/dist/routes.js.map +1 -0
- 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"}
|