@logto/js 2.0.0 → 2.0.1
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/lib/consts/index.cjs +83 -0
- package/lib/consts/index.d.ts +74 -0
- package/lib/consts/index.js +81 -0
- package/lib/core/fetch-token.cjs +47 -0
- package/lib/core/fetch-token.d.ts +36 -0
- package/lib/core/fetch-token.js +40 -0
- package/lib/core/fetch-token.test.d.ts +1 -0
- package/lib/core/index.d.ts +6 -0
- package/lib/core/oidc-config.cjs +13 -0
- package/lib/core/oidc-config.d.ts +15 -0
- package/lib/core/oidc-config.js +6 -0
- package/lib/core/oidc-config.test.d.ts +1 -0
- package/lib/core/revoke.cjs +14 -0
- package/lib/core/revoke.d.ts +2 -0
- package/lib/core/revoke.js +12 -0
- package/lib/core/revoke.test.d.ts +1 -0
- package/lib/core/sign-in.cjs +31 -0
- package/lib/core/sign-in.d.ts +14 -0
- package/lib/core/sign-in.js +29 -0
- package/lib/core/sign-in.test.d.ts +1 -0
- package/lib/core/sign-out.cjs +13 -0
- package/lib/core/sign-out.d.ts +7 -0
- package/lib/core/sign-out.js +11 -0
- package/lib/core/sign-out.test.d.ts +1 -0
- package/lib/core/user-info.cjs +7 -0
- package/lib/core/user-info.d.ts +20 -0
- package/lib/core/user-info.js +5 -0
- package/lib/core/user-info.test.d.ts +1 -0
- package/lib/index.cjs +56 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.js +12 -0
- package/lib/types/index.d.ts +6 -0
- package/lib/utils/arbitrary-object.cjs +5 -0
- package/lib/utils/arbitrary-object.d.ts +1 -0
- package/lib/utils/arbitrary-object.js +3 -0
- package/lib/utils/callback-uri.cjs +36 -0
- package/lib/utils/callback-uri.d.ts +2 -0
- package/lib/utils/callback-uri.js +33 -0
- package/lib/utils/callback-uri.test.d.ts +1 -0
- package/lib/utils/errors.cjs +45 -0
- package/lib/utils/errors.d.ts +31 -0
- package/lib/utils/errors.js +40 -0
- package/lib/utils/errors.test.d.ts +1 -0
- package/lib/utils/id-token.cjs +63 -0
- package/lib/utils/id-token.d.ts +19 -0
- package/lib/utils/id-token.js +60 -0
- package/lib/utils/id-token.test.d.ts +1 -0
- package/lib/utils/index.d.ts +5 -0
- package/lib/utils/scopes.cjs +15 -0
- package/lib/utils/scopes.d.ts +5 -0
- package/lib/utils/scopes.js +13 -0
- package/lib/utils/scopes.test.d.ts +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ContentType = {
|
|
4
|
+
formUrlEncoded: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
5
|
+
};
|
|
6
|
+
exports.TokenGrantType = void 0;
|
|
7
|
+
(function (TokenGrantType) {
|
|
8
|
+
TokenGrantType["AuthorizationCode"] = "authorization_code";
|
|
9
|
+
TokenGrantType["RefreshToken"] = "refresh_token";
|
|
10
|
+
})(exports.TokenGrantType || (exports.TokenGrantType = {}));
|
|
11
|
+
exports.QueryKey = void 0;
|
|
12
|
+
(function (QueryKey) {
|
|
13
|
+
QueryKey["ClientId"] = "client_id";
|
|
14
|
+
QueryKey["Code"] = "code";
|
|
15
|
+
QueryKey["CodeChallenge"] = "code_challenge";
|
|
16
|
+
QueryKey["CodeChallengeMethod"] = "code_challenge_method";
|
|
17
|
+
QueryKey["CodeVerifier"] = "code_verifier";
|
|
18
|
+
QueryKey["Error"] = "error";
|
|
19
|
+
QueryKey["ErrorDescription"] = "error_description";
|
|
20
|
+
QueryKey["GrantType"] = "grant_type";
|
|
21
|
+
QueryKey["IdToken"] = "id_token";
|
|
22
|
+
QueryKey["IdTokenHint"] = "id_token_hint";
|
|
23
|
+
QueryKey["PostLogoutRedirectUri"] = "post_logout_redirect_uri";
|
|
24
|
+
QueryKey["Prompt"] = "prompt";
|
|
25
|
+
QueryKey["RedirectUri"] = "redirect_uri";
|
|
26
|
+
QueryKey["RefreshToken"] = "refresh_token";
|
|
27
|
+
QueryKey["Resource"] = "resource";
|
|
28
|
+
QueryKey["ResponseType"] = "response_type";
|
|
29
|
+
QueryKey["Scope"] = "scope";
|
|
30
|
+
QueryKey["State"] = "state";
|
|
31
|
+
QueryKey["Token"] = "token";
|
|
32
|
+
// Need to align with the OIDC extraParams settings in core
|
|
33
|
+
QueryKey["InteractionMode"] = "interaction_mode";
|
|
34
|
+
})(exports.QueryKey || (exports.QueryKey = {}));
|
|
35
|
+
exports.Prompt = void 0;
|
|
36
|
+
(function (Prompt) {
|
|
37
|
+
Prompt["Consent"] = "consent";
|
|
38
|
+
Prompt["Login"] = "login";
|
|
39
|
+
})(exports.Prompt || (exports.Prompt = {}));
|
|
40
|
+
// TODO: @sijie @charles find a proper way to sync scopes constants with core
|
|
41
|
+
exports.ReservedScope = void 0;
|
|
42
|
+
(function (ReservedScope) {
|
|
43
|
+
ReservedScope["OpenId"] = "openid";
|
|
44
|
+
ReservedScope["OfflineAccess"] = "offline_access";
|
|
45
|
+
})(exports.ReservedScope || (exports.ReservedScope = {}));
|
|
46
|
+
/**
|
|
47
|
+
* Scopes for ID Token and Userinfo Endpoint.
|
|
48
|
+
*/
|
|
49
|
+
exports.UserScope = void 0;
|
|
50
|
+
(function (UserScope) {
|
|
51
|
+
/**
|
|
52
|
+
* Scope for basic user info.
|
|
53
|
+
*
|
|
54
|
+
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
|
55
|
+
*/
|
|
56
|
+
UserScope["Profile"] = "profile";
|
|
57
|
+
/**
|
|
58
|
+
* Scope for user email address.
|
|
59
|
+
*
|
|
60
|
+
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
|
61
|
+
*/
|
|
62
|
+
UserScope["Email"] = "email";
|
|
63
|
+
/**
|
|
64
|
+
* Scope for user phone number.
|
|
65
|
+
*
|
|
66
|
+
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
|
67
|
+
*/
|
|
68
|
+
UserScope["Phone"] = "phone";
|
|
69
|
+
/**
|
|
70
|
+
* Scope for user's custom data.
|
|
71
|
+
*
|
|
72
|
+
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
|
73
|
+
*/
|
|
74
|
+
UserScope["CustomData"] = "custom_data";
|
|
75
|
+
/**
|
|
76
|
+
* Scope for user's social identity details.
|
|
77
|
+
*
|
|
78
|
+
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
|
79
|
+
*/
|
|
80
|
+
UserScope["Identities"] = "identities";
|
|
81
|
+
})(exports.UserScope || (exports.UserScope = {}));
|
|
82
|
+
|
|
83
|
+
exports.ContentType = ContentType;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export declare const ContentType: {
|
|
2
|
+
formUrlEncoded: {
|
|
3
|
+
'Content-Type': string;
|
|
4
|
+
};
|
|
5
|
+
};
|
|
6
|
+
export declare enum TokenGrantType {
|
|
7
|
+
AuthorizationCode = "authorization_code",
|
|
8
|
+
RefreshToken = "refresh_token"
|
|
9
|
+
}
|
|
10
|
+
export declare enum QueryKey {
|
|
11
|
+
ClientId = "client_id",
|
|
12
|
+
Code = "code",
|
|
13
|
+
CodeChallenge = "code_challenge",
|
|
14
|
+
CodeChallengeMethod = "code_challenge_method",
|
|
15
|
+
CodeVerifier = "code_verifier",
|
|
16
|
+
Error = "error",
|
|
17
|
+
ErrorDescription = "error_description",
|
|
18
|
+
GrantType = "grant_type",
|
|
19
|
+
IdToken = "id_token",
|
|
20
|
+
IdTokenHint = "id_token_hint",
|
|
21
|
+
PostLogoutRedirectUri = "post_logout_redirect_uri",
|
|
22
|
+
Prompt = "prompt",
|
|
23
|
+
RedirectUri = "redirect_uri",
|
|
24
|
+
RefreshToken = "refresh_token",
|
|
25
|
+
Resource = "resource",
|
|
26
|
+
ResponseType = "response_type",
|
|
27
|
+
Scope = "scope",
|
|
28
|
+
State = "state",
|
|
29
|
+
Token = "token",
|
|
30
|
+
InteractionMode = "interaction_mode"
|
|
31
|
+
}
|
|
32
|
+
export declare enum Prompt {
|
|
33
|
+
Consent = "consent",
|
|
34
|
+
Login = "login"
|
|
35
|
+
}
|
|
36
|
+
export declare enum ReservedScope {
|
|
37
|
+
OpenId = "openid",
|
|
38
|
+
OfflineAccess = "offline_access"
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Scopes for ID Token and Userinfo Endpoint.
|
|
42
|
+
*/
|
|
43
|
+
export declare enum UserScope {
|
|
44
|
+
/**
|
|
45
|
+
* Scope for basic user info.
|
|
46
|
+
*
|
|
47
|
+
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
|
48
|
+
*/
|
|
49
|
+
Profile = "profile",
|
|
50
|
+
/**
|
|
51
|
+
* Scope for user email address.
|
|
52
|
+
*
|
|
53
|
+
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
|
54
|
+
*/
|
|
55
|
+
Email = "email",
|
|
56
|
+
/**
|
|
57
|
+
* Scope for user phone number.
|
|
58
|
+
*
|
|
59
|
+
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
|
60
|
+
*/
|
|
61
|
+
Phone = "phone",
|
|
62
|
+
/**
|
|
63
|
+
* Scope for user's custom data.
|
|
64
|
+
*
|
|
65
|
+
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
|
66
|
+
*/
|
|
67
|
+
CustomData = "custom_data",
|
|
68
|
+
/**
|
|
69
|
+
* Scope for user's social identity details.
|
|
70
|
+
*
|
|
71
|
+
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
|
72
|
+
*/
|
|
73
|
+
Identities = "identities"
|
|
74
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const ContentType = {
|
|
2
|
+
formUrlEncoded: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
3
|
+
};
|
|
4
|
+
var TokenGrantType;
|
|
5
|
+
(function (TokenGrantType) {
|
|
6
|
+
TokenGrantType["AuthorizationCode"] = "authorization_code";
|
|
7
|
+
TokenGrantType["RefreshToken"] = "refresh_token";
|
|
8
|
+
})(TokenGrantType || (TokenGrantType = {}));
|
|
9
|
+
var QueryKey;
|
|
10
|
+
(function (QueryKey) {
|
|
11
|
+
QueryKey["ClientId"] = "client_id";
|
|
12
|
+
QueryKey["Code"] = "code";
|
|
13
|
+
QueryKey["CodeChallenge"] = "code_challenge";
|
|
14
|
+
QueryKey["CodeChallengeMethod"] = "code_challenge_method";
|
|
15
|
+
QueryKey["CodeVerifier"] = "code_verifier";
|
|
16
|
+
QueryKey["Error"] = "error";
|
|
17
|
+
QueryKey["ErrorDescription"] = "error_description";
|
|
18
|
+
QueryKey["GrantType"] = "grant_type";
|
|
19
|
+
QueryKey["IdToken"] = "id_token";
|
|
20
|
+
QueryKey["IdTokenHint"] = "id_token_hint";
|
|
21
|
+
QueryKey["PostLogoutRedirectUri"] = "post_logout_redirect_uri";
|
|
22
|
+
QueryKey["Prompt"] = "prompt";
|
|
23
|
+
QueryKey["RedirectUri"] = "redirect_uri";
|
|
24
|
+
QueryKey["RefreshToken"] = "refresh_token";
|
|
25
|
+
QueryKey["Resource"] = "resource";
|
|
26
|
+
QueryKey["ResponseType"] = "response_type";
|
|
27
|
+
QueryKey["Scope"] = "scope";
|
|
28
|
+
QueryKey["State"] = "state";
|
|
29
|
+
QueryKey["Token"] = "token";
|
|
30
|
+
// Need to align with the OIDC extraParams settings in core
|
|
31
|
+
QueryKey["InteractionMode"] = "interaction_mode";
|
|
32
|
+
})(QueryKey || (QueryKey = {}));
|
|
33
|
+
var Prompt;
|
|
34
|
+
(function (Prompt) {
|
|
35
|
+
Prompt["Consent"] = "consent";
|
|
36
|
+
Prompt["Login"] = "login";
|
|
37
|
+
})(Prompt || (Prompt = {}));
|
|
38
|
+
// TODO: @sijie @charles find a proper way to sync scopes constants with core
|
|
39
|
+
var ReservedScope;
|
|
40
|
+
(function (ReservedScope) {
|
|
41
|
+
ReservedScope["OpenId"] = "openid";
|
|
42
|
+
ReservedScope["OfflineAccess"] = "offline_access";
|
|
43
|
+
})(ReservedScope || (ReservedScope = {}));
|
|
44
|
+
/**
|
|
45
|
+
* Scopes for ID Token and Userinfo Endpoint.
|
|
46
|
+
*/
|
|
47
|
+
var UserScope;
|
|
48
|
+
(function (UserScope) {
|
|
49
|
+
/**
|
|
50
|
+
* Scope for basic user info.
|
|
51
|
+
*
|
|
52
|
+
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
|
53
|
+
*/
|
|
54
|
+
UserScope["Profile"] = "profile";
|
|
55
|
+
/**
|
|
56
|
+
* Scope for user email address.
|
|
57
|
+
*
|
|
58
|
+
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
|
59
|
+
*/
|
|
60
|
+
UserScope["Email"] = "email";
|
|
61
|
+
/**
|
|
62
|
+
* Scope for user phone number.
|
|
63
|
+
*
|
|
64
|
+
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
|
65
|
+
*/
|
|
66
|
+
UserScope["Phone"] = "phone";
|
|
67
|
+
/**
|
|
68
|
+
* Scope for user's custom data.
|
|
69
|
+
*
|
|
70
|
+
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
|
71
|
+
*/
|
|
72
|
+
UserScope["CustomData"] = "custom_data";
|
|
73
|
+
/**
|
|
74
|
+
* Scope for user's social identity details.
|
|
75
|
+
*
|
|
76
|
+
* See {@link idTokenClaims} for mapped claims in ID Token and {@link userinfoClaims} for additional claims in Userinfo Endpoint.
|
|
77
|
+
*/
|
|
78
|
+
UserScope["Identities"] = "identities";
|
|
79
|
+
})(UserScope || (UserScope = {}));
|
|
80
|
+
|
|
81
|
+
export { ContentType, Prompt, QueryKey, ReservedScope, TokenGrantType, UserScope };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var camelcaseKeys = require('camelcase-keys');
|
|
4
|
+
var index = require('../consts/index.cjs');
|
|
5
|
+
|
|
6
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var camelcaseKeys__default = /*#__PURE__*/_interopDefault(camelcaseKeys);
|
|
9
|
+
|
|
10
|
+
const fetchTokenByAuthorizationCode = async ({ clientId, tokenEndpoint, redirectUri, codeVerifier, code, resource, }, requester) => {
|
|
11
|
+
const parameters = new URLSearchParams();
|
|
12
|
+
parameters.append(index.QueryKey.ClientId, clientId);
|
|
13
|
+
parameters.append(index.QueryKey.Code, code);
|
|
14
|
+
parameters.append(index.QueryKey.CodeVerifier, codeVerifier);
|
|
15
|
+
parameters.append(index.QueryKey.RedirectUri, redirectUri);
|
|
16
|
+
parameters.append(index.QueryKey.GrantType, index.TokenGrantType.AuthorizationCode);
|
|
17
|
+
if (resource) {
|
|
18
|
+
parameters.append(index.QueryKey.Resource, resource);
|
|
19
|
+
}
|
|
20
|
+
const snakeCaseCodeTokenResponse = await requester(tokenEndpoint, {
|
|
21
|
+
method: 'POST',
|
|
22
|
+
headers: index.ContentType.formUrlEncoded,
|
|
23
|
+
body: parameters,
|
|
24
|
+
});
|
|
25
|
+
return camelcaseKeys__default.default(snakeCaseCodeTokenResponse);
|
|
26
|
+
};
|
|
27
|
+
const fetchTokenByRefreshToken = async ({ clientId, tokenEndpoint, refreshToken, resource, scopes }, requester) => {
|
|
28
|
+
const parameters = new URLSearchParams();
|
|
29
|
+
parameters.append(index.QueryKey.ClientId, clientId);
|
|
30
|
+
parameters.append(index.QueryKey.RefreshToken, refreshToken);
|
|
31
|
+
parameters.append(index.QueryKey.GrantType, index.TokenGrantType.RefreshToken);
|
|
32
|
+
if (resource) {
|
|
33
|
+
parameters.append(index.QueryKey.Resource, resource);
|
|
34
|
+
}
|
|
35
|
+
if (scopes?.length) {
|
|
36
|
+
parameters.append(index.QueryKey.Scope, scopes.join(' '));
|
|
37
|
+
}
|
|
38
|
+
const snakeCaseRefreshTokenTokenResponse = await requester(tokenEndpoint, {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
headers: index.ContentType.formUrlEncoded,
|
|
41
|
+
body: parameters,
|
|
42
|
+
});
|
|
43
|
+
return camelcaseKeys__default.default(snakeCaseRefreshTokenTokenResponse);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
exports.fetchTokenByAuthorizationCode = fetchTokenByAuthorizationCode;
|
|
47
|
+
exports.fetchTokenByRefreshToken = fetchTokenByRefreshToken;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { KeysToCamelCase } from '@silverhand/essentials';
|
|
2
|
+
import type { Requester } from '../types/index.js';
|
|
3
|
+
export type FetchTokenByAuthorizationCodeParameters = {
|
|
4
|
+
clientId: string;
|
|
5
|
+
tokenEndpoint: string;
|
|
6
|
+
redirectUri: string;
|
|
7
|
+
codeVerifier: string;
|
|
8
|
+
code: string;
|
|
9
|
+
resource?: string;
|
|
10
|
+
};
|
|
11
|
+
export type FetchTokenByRefreshTokenParameters = {
|
|
12
|
+
clientId: string;
|
|
13
|
+
tokenEndpoint: string;
|
|
14
|
+
refreshToken: string;
|
|
15
|
+
resource?: string;
|
|
16
|
+
scopes?: string[];
|
|
17
|
+
};
|
|
18
|
+
type SnakeCaseCodeTokenResponse = {
|
|
19
|
+
access_token: string;
|
|
20
|
+
refresh_token?: string;
|
|
21
|
+
id_token: string;
|
|
22
|
+
scope: string;
|
|
23
|
+
expires_in: number;
|
|
24
|
+
};
|
|
25
|
+
export type CodeTokenResponse = KeysToCamelCase<SnakeCaseCodeTokenResponse>;
|
|
26
|
+
type SnakeCaseRefreshTokenTokenResponse = {
|
|
27
|
+
access_token: string;
|
|
28
|
+
refresh_token: string;
|
|
29
|
+
id_token?: string;
|
|
30
|
+
scope: string;
|
|
31
|
+
expires_in: number;
|
|
32
|
+
};
|
|
33
|
+
export type RefreshTokenTokenResponse = KeysToCamelCase<SnakeCaseRefreshTokenTokenResponse>;
|
|
34
|
+
export declare const fetchTokenByAuthorizationCode: ({ clientId, tokenEndpoint, redirectUri, codeVerifier, code, resource, }: FetchTokenByAuthorizationCodeParameters, requester: Requester) => Promise<CodeTokenResponse>;
|
|
35
|
+
export declare const fetchTokenByRefreshToken: ({ clientId, tokenEndpoint, refreshToken, resource, scopes }: FetchTokenByRefreshTokenParameters, requester: Requester) => Promise<RefreshTokenTokenResponse>;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import camelcaseKeys from 'camelcase-keys';
|
|
2
|
+
import { QueryKey, TokenGrantType, ContentType } from '../consts/index.js';
|
|
3
|
+
|
|
4
|
+
const fetchTokenByAuthorizationCode = async ({ clientId, tokenEndpoint, redirectUri, codeVerifier, code, resource, }, requester) => {
|
|
5
|
+
const parameters = new URLSearchParams();
|
|
6
|
+
parameters.append(QueryKey.ClientId, clientId);
|
|
7
|
+
parameters.append(QueryKey.Code, code);
|
|
8
|
+
parameters.append(QueryKey.CodeVerifier, codeVerifier);
|
|
9
|
+
parameters.append(QueryKey.RedirectUri, redirectUri);
|
|
10
|
+
parameters.append(QueryKey.GrantType, TokenGrantType.AuthorizationCode);
|
|
11
|
+
if (resource) {
|
|
12
|
+
parameters.append(QueryKey.Resource, resource);
|
|
13
|
+
}
|
|
14
|
+
const snakeCaseCodeTokenResponse = await requester(tokenEndpoint, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: ContentType.formUrlEncoded,
|
|
17
|
+
body: parameters,
|
|
18
|
+
});
|
|
19
|
+
return camelcaseKeys(snakeCaseCodeTokenResponse);
|
|
20
|
+
};
|
|
21
|
+
const fetchTokenByRefreshToken = async ({ clientId, tokenEndpoint, refreshToken, resource, scopes }, requester) => {
|
|
22
|
+
const parameters = new URLSearchParams();
|
|
23
|
+
parameters.append(QueryKey.ClientId, clientId);
|
|
24
|
+
parameters.append(QueryKey.RefreshToken, refreshToken);
|
|
25
|
+
parameters.append(QueryKey.GrantType, TokenGrantType.RefreshToken);
|
|
26
|
+
if (resource) {
|
|
27
|
+
parameters.append(QueryKey.Resource, resource);
|
|
28
|
+
}
|
|
29
|
+
if (scopes?.length) {
|
|
30
|
+
parameters.append(QueryKey.Scope, scopes.join(' '));
|
|
31
|
+
}
|
|
32
|
+
const snakeCaseRefreshTokenTokenResponse = await requester(tokenEndpoint, {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: ContentType.formUrlEncoded,
|
|
35
|
+
body: parameters,
|
|
36
|
+
});
|
|
37
|
+
return camelcaseKeys(snakeCaseRefreshTokenTokenResponse);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export { fetchTokenByAuthorizationCode, fetchTokenByRefreshToken };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var camelcaseKeys = require('camelcase-keys');
|
|
4
|
+
|
|
5
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
6
|
+
|
|
7
|
+
var camelcaseKeys__default = /*#__PURE__*/_interopDefault(camelcaseKeys);
|
|
8
|
+
|
|
9
|
+
const discoveryPath = '/oidc/.well-known/openid-configuration';
|
|
10
|
+
const fetchOidcConfig = async (endpoint, requester) => camelcaseKeys__default.default(await requester(endpoint));
|
|
11
|
+
|
|
12
|
+
exports.discoveryPath = discoveryPath;
|
|
13
|
+
exports.fetchOidcConfig = fetchOidcConfig;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { KeysToCamelCase } from '@silverhand/essentials';
|
|
2
|
+
import type { Requester } from '../types/index.js';
|
|
3
|
+
type OidcConfigSnakeCaseResponse = {
|
|
4
|
+
authorization_endpoint: string;
|
|
5
|
+
token_endpoint: string;
|
|
6
|
+
userinfo_endpoint: string;
|
|
7
|
+
end_session_endpoint: string;
|
|
8
|
+
revocation_endpoint: string;
|
|
9
|
+
jwks_uri: string;
|
|
10
|
+
issuer: string;
|
|
11
|
+
};
|
|
12
|
+
export declare const discoveryPath = "/oidc/.well-known/openid-configuration";
|
|
13
|
+
export type OidcConfigResponse = KeysToCamelCase<OidcConfigSnakeCaseResponse>;
|
|
14
|
+
export declare const fetchOidcConfig: (endpoint: string, requester: Requester) => Promise<OidcConfigResponse>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var index = require('../consts/index.cjs');
|
|
4
|
+
|
|
5
|
+
const revoke = async (revocationEndpoint, clientId, token, requester) => requester(revocationEndpoint, {
|
|
6
|
+
method: 'POST',
|
|
7
|
+
headers: index.ContentType.formUrlEncoded,
|
|
8
|
+
body: new URLSearchParams({
|
|
9
|
+
[index.QueryKey.ClientId]: clientId,
|
|
10
|
+
[index.QueryKey.Token]: token,
|
|
11
|
+
}),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
exports.revoke = revoke;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ContentType, QueryKey } from '../consts/index.js';
|
|
2
|
+
|
|
3
|
+
const revoke = async (revocationEndpoint, clientId, token, requester) => requester(revocationEndpoint, {
|
|
4
|
+
method: 'POST',
|
|
5
|
+
headers: ContentType.formUrlEncoded,
|
|
6
|
+
body: new URLSearchParams({
|
|
7
|
+
[QueryKey.ClientId]: clientId,
|
|
8
|
+
[QueryKey.Token]: token,
|
|
9
|
+
}),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export { revoke };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var index = require('../consts/index.cjs');
|
|
4
|
+
require('@silverhand/essentials');
|
|
5
|
+
require('jose');
|
|
6
|
+
var scopes = require('../utils/scopes.cjs');
|
|
7
|
+
|
|
8
|
+
const codeChallengeMethod = 'S256';
|
|
9
|
+
const responseType = 'code';
|
|
10
|
+
const generateSignInUri = ({ authorizationEndpoint, clientId, redirectUri, codeChallenge, state, scopes: scopes$1, resources, prompt, interactionMode, }) => {
|
|
11
|
+
const urlSearchParameters = new URLSearchParams({
|
|
12
|
+
[index.QueryKey.ClientId]: clientId,
|
|
13
|
+
[index.QueryKey.RedirectUri]: redirectUri,
|
|
14
|
+
[index.QueryKey.CodeChallenge]: codeChallenge,
|
|
15
|
+
[index.QueryKey.CodeChallengeMethod]: codeChallengeMethod,
|
|
16
|
+
[index.QueryKey.State]: state,
|
|
17
|
+
[index.QueryKey.ResponseType]: responseType,
|
|
18
|
+
[index.QueryKey.Prompt]: prompt ?? index.Prompt.Consent,
|
|
19
|
+
[index.QueryKey.Scope]: scopes.withDefaultScopes(scopes$1),
|
|
20
|
+
});
|
|
21
|
+
for (const resource of resources ?? []) {
|
|
22
|
+
urlSearchParameters.append(index.QueryKey.Resource, resource);
|
|
23
|
+
}
|
|
24
|
+
// Set interactionMode to signUp for a create account user experience
|
|
25
|
+
if (interactionMode) {
|
|
26
|
+
urlSearchParameters.append(index.QueryKey.InteractionMode, interactionMode);
|
|
27
|
+
}
|
|
28
|
+
return `${authorizationEndpoint}?${urlSearchParameters.toString()}`;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
exports.generateSignInUri = generateSignInUri;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Prompt } from '../consts/index.js';
|
|
2
|
+
import type { InteractionMode } from '../types/index.js';
|
|
3
|
+
export type SignInUriParameters = {
|
|
4
|
+
authorizationEndpoint: string;
|
|
5
|
+
clientId: string;
|
|
6
|
+
redirectUri: string;
|
|
7
|
+
codeChallenge: string;
|
|
8
|
+
state: string;
|
|
9
|
+
scopes?: string[];
|
|
10
|
+
resources?: string[];
|
|
11
|
+
prompt?: Prompt;
|
|
12
|
+
interactionMode?: InteractionMode;
|
|
13
|
+
};
|
|
14
|
+
export declare const generateSignInUri: ({ authorizationEndpoint, clientId, redirectUri, codeChallenge, state, scopes, resources, prompt, interactionMode, }: SignInUriParameters) => string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { QueryKey, Prompt } from '../consts/index.js';
|
|
2
|
+
import '@silverhand/essentials';
|
|
3
|
+
import 'jose';
|
|
4
|
+
import { withDefaultScopes } from '../utils/scopes.js';
|
|
5
|
+
|
|
6
|
+
const codeChallengeMethod = 'S256';
|
|
7
|
+
const responseType = 'code';
|
|
8
|
+
const generateSignInUri = ({ authorizationEndpoint, clientId, redirectUri, codeChallenge, state, scopes, resources, prompt, interactionMode, }) => {
|
|
9
|
+
const urlSearchParameters = new URLSearchParams({
|
|
10
|
+
[QueryKey.ClientId]: clientId,
|
|
11
|
+
[QueryKey.RedirectUri]: redirectUri,
|
|
12
|
+
[QueryKey.CodeChallenge]: codeChallenge,
|
|
13
|
+
[QueryKey.CodeChallengeMethod]: codeChallengeMethod,
|
|
14
|
+
[QueryKey.State]: state,
|
|
15
|
+
[QueryKey.ResponseType]: responseType,
|
|
16
|
+
[QueryKey.Prompt]: prompt ?? Prompt.Consent,
|
|
17
|
+
[QueryKey.Scope]: withDefaultScopes(scopes),
|
|
18
|
+
});
|
|
19
|
+
for (const resource of resources ?? []) {
|
|
20
|
+
urlSearchParameters.append(QueryKey.Resource, resource);
|
|
21
|
+
}
|
|
22
|
+
// Set interactionMode to signUp for a create account user experience
|
|
23
|
+
if (interactionMode) {
|
|
24
|
+
urlSearchParameters.append(QueryKey.InteractionMode, interactionMode);
|
|
25
|
+
}
|
|
26
|
+
return `${authorizationEndpoint}?${urlSearchParameters.toString()}`;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export { generateSignInUri };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var index = require('../consts/index.cjs');
|
|
4
|
+
|
|
5
|
+
const generateSignOutUri = ({ endSessionEndpoint, clientId, postLogoutRedirectUri, }) => {
|
|
6
|
+
const urlSearchParameters = new URLSearchParams({ [index.QueryKey.ClientId]: clientId });
|
|
7
|
+
if (postLogoutRedirectUri) {
|
|
8
|
+
urlSearchParameters.append(index.QueryKey.PostLogoutRedirectUri, postLogoutRedirectUri);
|
|
9
|
+
}
|
|
10
|
+
return `${endSessionEndpoint}?${urlSearchParameters.toString()}`;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
exports.generateSignOutUri = generateSignOutUri;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { QueryKey } from '../consts/index.js';
|
|
2
|
+
|
|
3
|
+
const generateSignOutUri = ({ endSessionEndpoint, clientId, postLogoutRedirectUri, }) => {
|
|
4
|
+
const urlSearchParameters = new URLSearchParams({ [QueryKey.ClientId]: clientId });
|
|
5
|
+
if (postLogoutRedirectUri) {
|
|
6
|
+
urlSearchParameters.append(QueryKey.PostLogoutRedirectUri, postLogoutRedirectUri);
|
|
7
|
+
}
|
|
8
|
+
return `${endSessionEndpoint}?${urlSearchParameters.toString()}`;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export { generateSignOutUri };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Nullable } from '@silverhand/essentials';
|
|
2
|
+
import type { Requester } from '../types/index.js';
|
|
3
|
+
type Identity = {
|
|
4
|
+
userId: string;
|
|
5
|
+
details?: Record<string, unknown>;
|
|
6
|
+
};
|
|
7
|
+
export type UserInfoResponse = {
|
|
8
|
+
sub: string;
|
|
9
|
+
name?: Nullable<string>;
|
|
10
|
+
username?: Nullable<string>;
|
|
11
|
+
picture?: Nullable<string>;
|
|
12
|
+
email?: Nullable<string>;
|
|
13
|
+
email_verified?: boolean;
|
|
14
|
+
phone_number?: Nullable<string>;
|
|
15
|
+
phone_number_verified?: boolean;
|
|
16
|
+
custom_data?: unknown;
|
|
17
|
+
identities?: Record<string, Identity>;
|
|
18
|
+
};
|
|
19
|
+
export declare const fetchUserInfo: (userInfoEndpoint: string, accessToken: string, requester: Requester) => Promise<UserInfoResponse>;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/lib/index.cjs
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fetchToken = require('./core/fetch-token.cjs');
|
|
4
|
+
var oidcConfig = require('./core/oidc-config.cjs');
|
|
5
|
+
var revoke = require('./core/revoke.cjs');
|
|
6
|
+
var signIn = require('./core/sign-in.cjs');
|
|
7
|
+
var signOut = require('./core/sign-out.cjs');
|
|
8
|
+
var userInfo = require('./core/user-info.cjs');
|
|
9
|
+
var callbackUri = require('./utils/callback-uri.cjs');
|
|
10
|
+
var errors = require('./utils/errors.cjs');
|
|
11
|
+
var idToken = require('./utils/id-token.cjs');
|
|
12
|
+
var scopes = require('./utils/scopes.cjs');
|
|
13
|
+
var arbitraryObject = require('./utils/arbitrary-object.cjs');
|
|
14
|
+
var index = require('./consts/index.cjs');
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
exports.fetchTokenByAuthorizationCode = fetchToken.fetchTokenByAuthorizationCode;
|
|
19
|
+
exports.fetchTokenByRefreshToken = fetchToken.fetchTokenByRefreshToken;
|
|
20
|
+
exports.discoveryPath = oidcConfig.discoveryPath;
|
|
21
|
+
exports.fetchOidcConfig = oidcConfig.fetchOidcConfig;
|
|
22
|
+
exports.revoke = revoke.revoke;
|
|
23
|
+
exports.generateSignInUri = signIn.generateSignInUri;
|
|
24
|
+
exports.generateSignOutUri = signOut.generateSignOutUri;
|
|
25
|
+
exports.fetchUserInfo = userInfo.fetchUserInfo;
|
|
26
|
+
exports.parseUriParameters = callbackUri.parseUriParameters;
|
|
27
|
+
exports.verifyAndParseCodeFromCallbackUri = callbackUri.verifyAndParseCodeFromCallbackUri;
|
|
28
|
+
exports.LogtoError = errors.LogtoError;
|
|
29
|
+
exports.LogtoRequestError = errors.LogtoRequestError;
|
|
30
|
+
exports.OidcError = errors.OidcError;
|
|
31
|
+
exports.isLogtoRequestError = errors.isLogtoRequestError;
|
|
32
|
+
exports.decodeIdToken = idToken.decodeIdToken;
|
|
33
|
+
exports.verifyIdToken = idToken.verifyIdToken;
|
|
34
|
+
exports.withDefaultScopes = scopes.withDefaultScopes;
|
|
35
|
+
exports.isArbitraryObject = arbitraryObject.isArbitraryObject;
|
|
36
|
+
exports.ContentType = index.ContentType;
|
|
37
|
+
Object.defineProperty(exports, 'Prompt', {
|
|
38
|
+
enumerable: true,
|
|
39
|
+
get: function () { return index.Prompt; }
|
|
40
|
+
});
|
|
41
|
+
Object.defineProperty(exports, 'QueryKey', {
|
|
42
|
+
enumerable: true,
|
|
43
|
+
get: function () { return index.QueryKey; }
|
|
44
|
+
});
|
|
45
|
+
Object.defineProperty(exports, 'ReservedScope', {
|
|
46
|
+
enumerable: true,
|
|
47
|
+
get: function () { return index.ReservedScope; }
|
|
48
|
+
});
|
|
49
|
+
Object.defineProperty(exports, 'TokenGrantType', {
|
|
50
|
+
enumerable: true,
|
|
51
|
+
get: function () { return index.TokenGrantType; }
|
|
52
|
+
});
|
|
53
|
+
Object.defineProperty(exports, 'UserScope', {
|
|
54
|
+
enumerable: true,
|
|
55
|
+
get: function () { return index.UserScope; }
|
|
56
|
+
});
|
package/lib/index.d.ts
ADDED
package/lib/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { fetchTokenByAuthorizationCode, fetchTokenByRefreshToken } from './core/fetch-token.js';
|
|
2
|
+
export { discoveryPath, fetchOidcConfig } from './core/oidc-config.js';
|
|
3
|
+
export { revoke } from './core/revoke.js';
|
|
4
|
+
export { generateSignInUri } from './core/sign-in.js';
|
|
5
|
+
export { generateSignOutUri } from './core/sign-out.js';
|
|
6
|
+
export { fetchUserInfo } from './core/user-info.js';
|
|
7
|
+
export { parseUriParameters, verifyAndParseCodeFromCallbackUri } from './utils/callback-uri.js';
|
|
8
|
+
export { LogtoError, LogtoRequestError, OidcError, isLogtoRequestError } from './utils/errors.js';
|
|
9
|
+
export { decodeIdToken, verifyIdToken } from './utils/id-token.js';
|
|
10
|
+
export { withDefaultScopes } from './utils/scopes.js';
|
|
11
|
+
export { isArbitraryObject } from './utils/arbitrary-object.js';
|
|
12
|
+
export { ContentType, Prompt, QueryKey, ReservedScope, TokenGrantType, UserScope } from './consts/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const isArbitraryObject: (data: unknown) => data is Record<string, unknown>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var essentials = require('@silverhand/essentials');
|
|
4
|
+
var index = require('../consts/index.cjs');
|
|
5
|
+
var errors = require('./errors.cjs');
|
|
6
|
+
|
|
7
|
+
const parseUriParameters = (uri) => {
|
|
8
|
+
const [, queryString = ''] = uri.split('?');
|
|
9
|
+
return new URLSearchParams(queryString);
|
|
10
|
+
};
|
|
11
|
+
const verifyAndParseCodeFromCallbackUri = (callbackUri, redirectUri, state) => {
|
|
12
|
+
if (!callbackUri.startsWith(redirectUri)) {
|
|
13
|
+
throw new errors.LogtoError('callback_uri_verification.redirect_uri_mismatched');
|
|
14
|
+
}
|
|
15
|
+
const uriParameters = parseUriParameters(callbackUri);
|
|
16
|
+
const error = essentials.conditional(uriParameters.get(index.QueryKey.Error));
|
|
17
|
+
const errorDescription = essentials.conditional(uriParameters.get(index.QueryKey.ErrorDescription));
|
|
18
|
+
if (error) {
|
|
19
|
+
throw new errors.LogtoError('callback_uri_verification.error_found', new errors.OidcError(error, errorDescription));
|
|
20
|
+
}
|
|
21
|
+
const stateFromCallbackUri = uriParameters.get(index.QueryKey.State);
|
|
22
|
+
if (!stateFromCallbackUri) {
|
|
23
|
+
throw new errors.LogtoError('callback_uri_verification.missing_state');
|
|
24
|
+
}
|
|
25
|
+
if (stateFromCallbackUri !== state) {
|
|
26
|
+
throw new errors.LogtoError('callback_uri_verification.state_mismatched');
|
|
27
|
+
}
|
|
28
|
+
const code = uriParameters.get(index.QueryKey.Code);
|
|
29
|
+
if (!code) {
|
|
30
|
+
throw new errors.LogtoError('callback_uri_verification.missing_code');
|
|
31
|
+
}
|
|
32
|
+
return code;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
exports.parseUriParameters = parseUriParameters;
|
|
36
|
+
exports.verifyAndParseCodeFromCallbackUri = verifyAndParseCodeFromCallbackUri;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { conditional } from '@silverhand/essentials';
|
|
2
|
+
import { QueryKey } from '../consts/index.js';
|
|
3
|
+
import { LogtoError, OidcError } from './errors.js';
|
|
4
|
+
|
|
5
|
+
const parseUriParameters = (uri) => {
|
|
6
|
+
const [, queryString = ''] = uri.split('?');
|
|
7
|
+
return new URLSearchParams(queryString);
|
|
8
|
+
};
|
|
9
|
+
const verifyAndParseCodeFromCallbackUri = (callbackUri, redirectUri, state) => {
|
|
10
|
+
if (!callbackUri.startsWith(redirectUri)) {
|
|
11
|
+
throw new LogtoError('callback_uri_verification.redirect_uri_mismatched');
|
|
12
|
+
}
|
|
13
|
+
const uriParameters = parseUriParameters(callbackUri);
|
|
14
|
+
const error = conditional(uriParameters.get(QueryKey.Error));
|
|
15
|
+
const errorDescription = conditional(uriParameters.get(QueryKey.ErrorDescription));
|
|
16
|
+
if (error) {
|
|
17
|
+
throw new LogtoError('callback_uri_verification.error_found', new OidcError(error, errorDescription));
|
|
18
|
+
}
|
|
19
|
+
const stateFromCallbackUri = uriParameters.get(QueryKey.State);
|
|
20
|
+
if (!stateFromCallbackUri) {
|
|
21
|
+
throw new LogtoError('callback_uri_verification.missing_state');
|
|
22
|
+
}
|
|
23
|
+
if (stateFromCallbackUri !== state) {
|
|
24
|
+
throw new LogtoError('callback_uri_verification.state_mismatched');
|
|
25
|
+
}
|
|
26
|
+
const code = uriParameters.get(QueryKey.Code);
|
|
27
|
+
if (!code) {
|
|
28
|
+
throw new LogtoError('callback_uri_verification.missing_code');
|
|
29
|
+
}
|
|
30
|
+
return code;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export { parseUriParameters, verifyAndParseCodeFromCallbackUri };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var arbitraryObject = require('./arbitrary-object.cjs');
|
|
4
|
+
|
|
5
|
+
const logtoErrorCodes = Object.freeze({
|
|
6
|
+
'id_token.invalid_iat': 'Invalid issued at time in the ID token',
|
|
7
|
+
'id_token.invalid_token': 'Invalid ID token',
|
|
8
|
+
'callback_uri_verification.redirect_uri_mismatched': 'The callback URI mismatches the redirect URI.',
|
|
9
|
+
'callback_uri_verification.error_found': 'Error found in the callback URI',
|
|
10
|
+
'callback_uri_verification.missing_state': 'Missing state in the callback URI',
|
|
11
|
+
'callback_uri_verification.state_mismatched': 'State mismatched in the callback URI',
|
|
12
|
+
'callback_uri_verification.missing_code': 'Missing code in the callback URI',
|
|
13
|
+
crypto_subtle_unavailable: 'Crypto.subtle is unavailable in insecure contexts (non-HTTPS).',
|
|
14
|
+
unexpected_response_error: 'Unexpected response error from the server.',
|
|
15
|
+
});
|
|
16
|
+
class LogtoError extends Error {
|
|
17
|
+
constructor(code, data) {
|
|
18
|
+
super(logtoErrorCodes[code]);
|
|
19
|
+
this.code = code;
|
|
20
|
+
this.data = data;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const isLogtoRequestError = (data) => {
|
|
24
|
+
if (!arbitraryObject.isArbitraryObject(data)) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return typeof data.code === 'string' && typeof data.message === 'string';
|
|
28
|
+
};
|
|
29
|
+
class LogtoRequestError extends Error {
|
|
30
|
+
constructor(code, message) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.code = code;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
class OidcError {
|
|
36
|
+
constructor(error, errorDescription) {
|
|
37
|
+
this.error = error;
|
|
38
|
+
this.errorDescription = errorDescription;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
exports.LogtoError = LogtoError;
|
|
43
|
+
exports.LogtoRequestError = LogtoRequestError;
|
|
44
|
+
exports.OidcError = OidcError;
|
|
45
|
+
exports.isLogtoRequestError = isLogtoRequestError;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
declare const logtoErrorCodes: Readonly<{
|
|
2
|
+
'id_token.invalid_iat': "Invalid issued at time in the ID token";
|
|
3
|
+
'id_token.invalid_token': "Invalid ID token";
|
|
4
|
+
'callback_uri_verification.redirect_uri_mismatched': "The callback URI mismatches the redirect URI.";
|
|
5
|
+
'callback_uri_verification.error_found': "Error found in the callback URI";
|
|
6
|
+
'callback_uri_verification.missing_state': "Missing state in the callback URI";
|
|
7
|
+
'callback_uri_verification.state_mismatched': "State mismatched in the callback URI";
|
|
8
|
+
'callback_uri_verification.missing_code': "Missing code in the callback URI";
|
|
9
|
+
crypto_subtle_unavailable: "Crypto.subtle is unavailable in insecure contexts (non-HTTPS).";
|
|
10
|
+
unexpected_response_error: "Unexpected response error from the server.";
|
|
11
|
+
}>;
|
|
12
|
+
export type LogtoErrorCode = keyof typeof logtoErrorCodes;
|
|
13
|
+
export declare class LogtoError extends Error {
|
|
14
|
+
code: LogtoErrorCode;
|
|
15
|
+
data: unknown;
|
|
16
|
+
constructor(code: LogtoErrorCode, data?: unknown);
|
|
17
|
+
}
|
|
18
|
+
export declare const isLogtoRequestError: (data: unknown) => data is {
|
|
19
|
+
code: string;
|
|
20
|
+
message: string;
|
|
21
|
+
};
|
|
22
|
+
export declare class LogtoRequestError extends Error {
|
|
23
|
+
code: string;
|
|
24
|
+
constructor(code: string, message: string);
|
|
25
|
+
}
|
|
26
|
+
export declare class OidcError {
|
|
27
|
+
error: string;
|
|
28
|
+
errorDescription?: string | undefined;
|
|
29
|
+
constructor(error: string, errorDescription?: string | undefined);
|
|
30
|
+
}
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { isArbitraryObject } from './arbitrary-object.js';
|
|
2
|
+
|
|
3
|
+
const logtoErrorCodes = Object.freeze({
|
|
4
|
+
'id_token.invalid_iat': 'Invalid issued at time in the ID token',
|
|
5
|
+
'id_token.invalid_token': 'Invalid ID token',
|
|
6
|
+
'callback_uri_verification.redirect_uri_mismatched': 'The callback URI mismatches the redirect URI.',
|
|
7
|
+
'callback_uri_verification.error_found': 'Error found in the callback URI',
|
|
8
|
+
'callback_uri_verification.missing_state': 'Missing state in the callback URI',
|
|
9
|
+
'callback_uri_verification.state_mismatched': 'State mismatched in the callback URI',
|
|
10
|
+
'callback_uri_verification.missing_code': 'Missing code in the callback URI',
|
|
11
|
+
crypto_subtle_unavailable: 'Crypto.subtle is unavailable in insecure contexts (non-HTTPS).',
|
|
12
|
+
unexpected_response_error: 'Unexpected response error from the server.',
|
|
13
|
+
});
|
|
14
|
+
class LogtoError extends Error {
|
|
15
|
+
constructor(code, data) {
|
|
16
|
+
super(logtoErrorCodes[code]);
|
|
17
|
+
this.code = code;
|
|
18
|
+
this.data = data;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const isLogtoRequestError = (data) => {
|
|
22
|
+
if (!isArbitraryObject(data)) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return typeof data.code === 'string' && typeof data.message === 'string';
|
|
26
|
+
};
|
|
27
|
+
class LogtoRequestError extends Error {
|
|
28
|
+
constructor(code, message) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.code = code;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
class OidcError {
|
|
34
|
+
constructor(error, errorDescription) {
|
|
35
|
+
this.error = error;
|
|
36
|
+
this.errorDescription = errorDescription;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { LogtoError, LogtoRequestError, OidcError, isLogtoRequestError };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var essentials = require('@silverhand/essentials');
|
|
4
|
+
var jose = require('jose');
|
|
5
|
+
var arbitraryObject = require('./arbitrary-object.cjs');
|
|
6
|
+
var errors = require('./errors.cjs');
|
|
7
|
+
|
|
8
|
+
const issuedAtTimeTolerance = 60;
|
|
9
|
+
/* eslint-disable complexity */
|
|
10
|
+
/**
|
|
11
|
+
* @link [ID Token](https://openid.net/specs/openid-connect-core-1_0.html#IDToken)
|
|
12
|
+
*/
|
|
13
|
+
function assertIdTokenClaims(data) {
|
|
14
|
+
if (!arbitraryObject.isArbitraryObject(data)) {
|
|
15
|
+
throw new TypeError('IdToken is expected to be an object');
|
|
16
|
+
}
|
|
17
|
+
for (const key of ['iss', 'sub', 'aud']) {
|
|
18
|
+
if (typeof data[key] !== 'string') {
|
|
19
|
+
throw new TypeError(`At path: IdToken.${key}: expected a string`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
for (const key of ['exp', 'iat']) {
|
|
23
|
+
if (typeof data[key] !== 'number') {
|
|
24
|
+
throw new TypeError(`At path: IdToken.${key}: expected a number`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
for (const key of ['at_hash', 'name', 'username', 'picture', 'email', 'phone_number']) {
|
|
28
|
+
if (data[key] === undefined) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (typeof data[key] !== 'string' && data[key] !== null) {
|
|
32
|
+
throw new TypeError(`At path: IdToken.${key}: expected null or a string`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
for (const key of ['email_verified', 'phone_number_verified']) {
|
|
36
|
+
if (data[key] === undefined) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (typeof data[key] !== 'boolean') {
|
|
40
|
+
throw new TypeError(`At path: IdToken.${key}: expected a boolean`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/* eslint-enable complexity */
|
|
45
|
+
const verifyIdToken = async (idToken, clientId, issuer, jwks) => {
|
|
46
|
+
const result = await jose.jwtVerify(idToken, jwks, { audience: clientId, issuer });
|
|
47
|
+
if (Math.abs((result.payload.iat ?? 0) - Date.now() / 1000) > issuedAtTimeTolerance) {
|
|
48
|
+
throw new errors.LogtoError('id_token.invalid_iat');
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
const decodeIdToken = (token) => {
|
|
52
|
+
const { 1: encodedPayload } = token.split('.');
|
|
53
|
+
if (!encodedPayload) {
|
|
54
|
+
throw new errors.LogtoError('id_token.invalid_token');
|
|
55
|
+
}
|
|
56
|
+
const json = essentials.urlSafeBase64.decode(encodedPayload);
|
|
57
|
+
const idTokenClaims = JSON.parse(json);
|
|
58
|
+
assertIdTokenClaims(idTokenClaims);
|
|
59
|
+
return idTokenClaims;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
exports.decodeIdToken = decodeIdToken;
|
|
63
|
+
exports.verifyIdToken = verifyIdToken;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Nullable } from '@silverhand/essentials';
|
|
2
|
+
import type { JWTVerifyGetKey } from 'jose';
|
|
3
|
+
export type IdTokenClaims = {
|
|
4
|
+
iss: string;
|
|
5
|
+
sub: string;
|
|
6
|
+
aud: string;
|
|
7
|
+
exp: number;
|
|
8
|
+
iat: number;
|
|
9
|
+
at_hash?: Nullable<string>;
|
|
10
|
+
name?: Nullable<string>;
|
|
11
|
+
username?: Nullable<string>;
|
|
12
|
+
picture?: Nullable<string>;
|
|
13
|
+
email?: Nullable<string>;
|
|
14
|
+
email_verified?: boolean;
|
|
15
|
+
phone_number?: Nullable<string>;
|
|
16
|
+
phone_number_verified?: boolean;
|
|
17
|
+
};
|
|
18
|
+
export declare const verifyIdToken: (idToken: string, clientId: string, issuer: string, jwks: JWTVerifyGetKey) => Promise<void>;
|
|
19
|
+
export declare const decodeIdToken: (token: string) => IdTokenClaims;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { urlSafeBase64 } from '@silverhand/essentials';
|
|
2
|
+
import { jwtVerify } from 'jose';
|
|
3
|
+
import { isArbitraryObject } from './arbitrary-object.js';
|
|
4
|
+
import { LogtoError } from './errors.js';
|
|
5
|
+
|
|
6
|
+
const issuedAtTimeTolerance = 60;
|
|
7
|
+
/* eslint-disable complexity */
|
|
8
|
+
/**
|
|
9
|
+
* @link [ID Token](https://openid.net/specs/openid-connect-core-1_0.html#IDToken)
|
|
10
|
+
*/
|
|
11
|
+
function assertIdTokenClaims(data) {
|
|
12
|
+
if (!isArbitraryObject(data)) {
|
|
13
|
+
throw new TypeError('IdToken is expected to be an object');
|
|
14
|
+
}
|
|
15
|
+
for (const key of ['iss', 'sub', 'aud']) {
|
|
16
|
+
if (typeof data[key] !== 'string') {
|
|
17
|
+
throw new TypeError(`At path: IdToken.${key}: expected a string`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
for (const key of ['exp', 'iat']) {
|
|
21
|
+
if (typeof data[key] !== 'number') {
|
|
22
|
+
throw new TypeError(`At path: IdToken.${key}: expected a number`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const key of ['at_hash', 'name', 'username', 'picture', 'email', 'phone_number']) {
|
|
26
|
+
if (data[key] === undefined) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (typeof data[key] !== 'string' && data[key] !== null) {
|
|
30
|
+
throw new TypeError(`At path: IdToken.${key}: expected null or a string`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
for (const key of ['email_verified', 'phone_number_verified']) {
|
|
34
|
+
if (data[key] === undefined) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (typeof data[key] !== 'boolean') {
|
|
38
|
+
throw new TypeError(`At path: IdToken.${key}: expected a boolean`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/* eslint-enable complexity */
|
|
43
|
+
const verifyIdToken = async (idToken, clientId, issuer, jwks) => {
|
|
44
|
+
const result = await jwtVerify(idToken, jwks, { audience: clientId, issuer });
|
|
45
|
+
if (Math.abs((result.payload.iat ?? 0) - Date.now() / 1000) > issuedAtTimeTolerance) {
|
|
46
|
+
throw new LogtoError('id_token.invalid_iat');
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const decodeIdToken = (token) => {
|
|
50
|
+
const { 1: encodedPayload } = token.split('.');
|
|
51
|
+
if (!encodedPayload) {
|
|
52
|
+
throw new LogtoError('id_token.invalid_token');
|
|
53
|
+
}
|
|
54
|
+
const json = urlSafeBase64.decode(encodedPayload);
|
|
55
|
+
const idTokenClaims = JSON.parse(json);
|
|
56
|
+
assertIdTokenClaims(idTokenClaims);
|
|
57
|
+
return idTokenClaims;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export { decodeIdToken, verifyIdToken };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var index = require('../consts/index.cjs');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param originalScopes
|
|
7
|
+
* @return scopes should contain all default scopes (`openid`, `offline_access` and `profile`)
|
|
8
|
+
*/
|
|
9
|
+
const withDefaultScopes = (originalScopes) => {
|
|
10
|
+
const reservedScopes = Object.values(index.ReservedScope);
|
|
11
|
+
const uniqueScopes = new Set([...reservedScopes, index.UserScope.Profile, ...(originalScopes ?? [])]);
|
|
12
|
+
return Array.from(uniqueScopes).join(' ');
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
exports.withDefaultScopes = withDefaultScopes;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ReservedScope, UserScope } from '../consts/index.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param originalScopes
|
|
5
|
+
* @return scopes should contain all default scopes (`openid`, `offline_access` and `profile`)
|
|
6
|
+
*/
|
|
7
|
+
const withDefaultScopes = (originalScopes) => {
|
|
8
|
+
const reservedScopes = Object.values(ReservedScope);
|
|
9
|
+
const uniqueScopes = new Set([...reservedScopes, UserScope.Profile, ...(originalScopes ?? [])]);
|
|
10
|
+
return Array.from(uniqueScopes).join(' ');
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export { withDefaultScopes };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|