@logto/core-kit 2.9.0 → 2.10.0
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/custom-ui-csp.d.ts +50 -0
- package/lib/custom-ui-csp.js +121 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/openid.d.ts +1 -0
- package/lib/openid.js +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const customUiCspDirectives: readonly ["scriptSrc", "connectSrc"];
|
|
3
|
+
export type CustomUiCspDirective = (typeof customUiCspDirectives)[number];
|
|
4
|
+
export type CustomUiCsp = {
|
|
5
|
+
scriptSrc?: string[];
|
|
6
|
+
connectSrc?: string[];
|
|
7
|
+
};
|
|
8
|
+
export declare const customUiCspGuard: z.ZodObject<{
|
|
9
|
+
scriptSrc: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
10
|
+
connectSrc: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
11
|
+
}, "strict", z.ZodTypeAny, {
|
|
12
|
+
scriptSrc?: string[] | undefined;
|
|
13
|
+
connectSrc?: string[] | undefined;
|
|
14
|
+
}, {
|
|
15
|
+
scriptSrc?: string[] | undefined;
|
|
16
|
+
connectSrc?: string[] | undefined;
|
|
17
|
+
}>;
|
|
18
|
+
export declare enum CustomUiCspSourceValidationErrorCode {
|
|
19
|
+
EmptySource = "empty_source",
|
|
20
|
+
SemicolonNotAllowed = "semicolon_not_allowed",
|
|
21
|
+
CspKeywordNotSupported = "csp_keyword_not_supported",
|
|
22
|
+
MalformedUrl = "malformed_url",
|
|
23
|
+
DisallowedUrlParts = "disallowed_url_parts",
|
|
24
|
+
UnsupportedScheme = "unsupported_scheme",
|
|
25
|
+
MalformedWildcardHost = "malformed_wildcard_host",
|
|
26
|
+
MalformedHost = "malformed_host"
|
|
27
|
+
}
|
|
28
|
+
export type CustomUiCspSourceValidationError = {
|
|
29
|
+
readonly directive: CustomUiCspDirective;
|
|
30
|
+
readonly source: string;
|
|
31
|
+
readonly code: CustomUiCspSourceValidationErrorCode;
|
|
32
|
+
};
|
|
33
|
+
export type CustomUiCspSourceValidationResult = {
|
|
34
|
+
readonly isValid: true;
|
|
35
|
+
readonly value: string;
|
|
36
|
+
} | {
|
|
37
|
+
readonly isValid: false;
|
|
38
|
+
readonly code: CustomUiCspSourceValidationErrorCode;
|
|
39
|
+
};
|
|
40
|
+
type CustomUiCspValidationOptions = {
|
|
41
|
+
readonly isProduction?: boolean;
|
|
42
|
+
};
|
|
43
|
+
export type NormalizeCustomUiCspResult = {
|
|
44
|
+
readonly customUiCsp: CustomUiCsp;
|
|
45
|
+
readonly errors: CustomUiCspSourceValidationError[];
|
|
46
|
+
};
|
|
47
|
+
export declare const normalizeCustomUiCspSourceExpression: (directive: CustomUiCspDirective, rawSource: string, options?: CustomUiCspValidationOptions) => CustomUiCspSourceValidationResult;
|
|
48
|
+
export declare const normalizeCustomUiCsp: (customUiCsp: CustomUiCsp, options?: CustomUiCspValidationOptions) => NormalizeCustomUiCspResult;
|
|
49
|
+
export declare const hasCustomUiCspSources: (customUiCsp?: CustomUiCsp) => boolean;
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const customUiCspDirectives = Object.freeze(['scriptSrc', 'connectSrc']);
|
|
3
|
+
export const customUiCspGuard = z
|
|
4
|
+
.object({
|
|
5
|
+
scriptSrc: z.string().array().optional(),
|
|
6
|
+
connectSrc: z.string().array().optional(),
|
|
7
|
+
})
|
|
8
|
+
.strict();
|
|
9
|
+
export var CustomUiCspSourceValidationErrorCode;
|
|
10
|
+
(function (CustomUiCspSourceValidationErrorCode) {
|
|
11
|
+
CustomUiCspSourceValidationErrorCode["EmptySource"] = "empty_source";
|
|
12
|
+
CustomUiCspSourceValidationErrorCode["SemicolonNotAllowed"] = "semicolon_not_allowed";
|
|
13
|
+
CustomUiCspSourceValidationErrorCode["CspKeywordNotSupported"] = "csp_keyword_not_supported";
|
|
14
|
+
CustomUiCspSourceValidationErrorCode["MalformedUrl"] = "malformed_url";
|
|
15
|
+
CustomUiCspSourceValidationErrorCode["DisallowedUrlParts"] = "disallowed_url_parts";
|
|
16
|
+
CustomUiCspSourceValidationErrorCode["UnsupportedScheme"] = "unsupported_scheme";
|
|
17
|
+
CustomUiCspSourceValidationErrorCode["MalformedWildcardHost"] = "malformed_wildcard_host";
|
|
18
|
+
CustomUiCspSourceValidationErrorCode["MalformedHost"] = "malformed_host";
|
|
19
|
+
})(CustomUiCspSourceValidationErrorCode || (CustomUiCspSourceValidationErrorCode = {}));
|
|
20
|
+
const validHostLabelRegEx = /^[\da-z](?:[\da-z-]{0,61}[\da-z])?$/;
|
|
21
|
+
const isValidHostLabel = (label) => validHostLabelRegEx.test(label);
|
|
22
|
+
const validateHostname = (hostname) => {
|
|
23
|
+
if (hostname === 'localhost') {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const labels = hostname.split('.');
|
|
27
|
+
const wildcardCount = labels.filter((label) => label === '*').length;
|
|
28
|
+
if (wildcardCount > 0) {
|
|
29
|
+
return labels[0] === '*' &&
|
|
30
|
+
wildcardCount === 1 &&
|
|
31
|
+
labels.length >= 3 &&
|
|
32
|
+
labels.slice(1).every((label) => isValidHostLabel(label))
|
|
33
|
+
? undefined
|
|
34
|
+
: CustomUiCspSourceValidationErrorCode.MalformedWildcardHost;
|
|
35
|
+
}
|
|
36
|
+
return labels.length >= 2 && labels.every((label) => isValidHostLabel(label))
|
|
37
|
+
? undefined
|
|
38
|
+
: CustomUiCspSourceValidationErrorCode.MalformedHost;
|
|
39
|
+
};
|
|
40
|
+
const validateScheme = (directive, url, { isProduction = false }) => {
|
|
41
|
+
const isLocalhostHttpSource = !isProduction && url.protocol === 'http:' && url.hostname === 'localhost' && url.port;
|
|
42
|
+
if (isLocalhostHttpSource) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const allowedSchemes = directive === 'connectSrc' ? ['https:', 'wss:'] : ['https:'];
|
|
46
|
+
return allowedSchemes.includes(url.protocol)
|
|
47
|
+
? undefined
|
|
48
|
+
: CustomUiCspSourceValidationErrorCode.UnsupportedScheme;
|
|
49
|
+
};
|
|
50
|
+
const getInvalidPlainSourceCode = (source) => {
|
|
51
|
+
if (!source) {
|
|
52
|
+
return CustomUiCspSourceValidationErrorCode.EmptySource;
|
|
53
|
+
}
|
|
54
|
+
if (source.includes(';')) {
|
|
55
|
+
return CustomUiCspSourceValidationErrorCode.SemicolonNotAllowed;
|
|
56
|
+
}
|
|
57
|
+
return source.includes("'")
|
|
58
|
+
? CustomUiCspSourceValidationErrorCode.CspKeywordNotSupported
|
|
59
|
+
: undefined;
|
|
60
|
+
};
|
|
61
|
+
const hasDisallowedUrlParts = (url) => Boolean(url.username || url.password || url.search || url.hash);
|
|
62
|
+
const normalizeParsedSourceExpression = (directive, url, options) => {
|
|
63
|
+
if (hasDisallowedUrlParts(url)) {
|
|
64
|
+
return {
|
|
65
|
+
isValid: false,
|
|
66
|
+
code: CustomUiCspSourceValidationErrorCode.DisallowedUrlParts,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const invalidSchemeCode = validateScheme(directive, url, options ?? {});
|
|
70
|
+
if (invalidSchemeCode) {
|
|
71
|
+
return { isValid: false, code: invalidSchemeCode };
|
|
72
|
+
}
|
|
73
|
+
const invalidHostCode = validateHostname(url.hostname);
|
|
74
|
+
if (invalidHostCode) {
|
|
75
|
+
return { isValid: false, code: invalidHostCode };
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
isValid: true,
|
|
79
|
+
value: `${url.protocol}//${url.host}${url.pathname === '/' ? '' : url.pathname}`,
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
export const normalizeCustomUiCspSourceExpression = (directive, rawSource, options) => {
|
|
83
|
+
const source = rawSource.trim();
|
|
84
|
+
const invalidSourceCode = getInvalidPlainSourceCode(source);
|
|
85
|
+
if (invalidSourceCode) {
|
|
86
|
+
return { isValid: false, code: invalidSourceCode };
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
return normalizeParsedSourceExpression(directive, new URL(source), options);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return { isValid: false, code: CustomUiCspSourceValidationErrorCode.MalformedUrl };
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const normalizeDirectiveSources = (directive, sources, options) => {
|
|
96
|
+
const results = sources.map((source) => ({
|
|
97
|
+
source,
|
|
98
|
+
result: normalizeCustomUiCspSourceExpression(directive, source, options),
|
|
99
|
+
}));
|
|
100
|
+
return {
|
|
101
|
+
sources: [...new Set(results.flatMap(({ result }) => (result.isValid ? [result.value] : [])))],
|
|
102
|
+
errors: results.flatMap(({ source, result }) => result.isValid ? [] : [{ directive, source, code: result.code }]),
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
export const normalizeCustomUiCsp = (customUiCsp, options) => customUiCspDirectives.reduce(({ customUiCsp: normalizedCustomUiCsp, errors }, directive) => {
|
|
106
|
+
const sources = customUiCsp[directive];
|
|
107
|
+
if (!sources?.length) {
|
|
108
|
+
return { customUiCsp: normalizedCustomUiCsp, errors };
|
|
109
|
+
}
|
|
110
|
+
const result = normalizeDirectiveSources(directive, sources, options);
|
|
111
|
+
return {
|
|
112
|
+
customUiCsp: result.sources.length > 0
|
|
113
|
+
? {
|
|
114
|
+
...normalizedCustomUiCsp,
|
|
115
|
+
[directive]: result.sources,
|
|
116
|
+
}
|
|
117
|
+
: normalizedCustomUiCsp,
|
|
118
|
+
errors: [...errors, ...result.errors],
|
|
119
|
+
};
|
|
120
|
+
}, { customUiCsp: {}, errors: [] });
|
|
121
|
+
export const hasCustomUiCspSources = (customUiCsp) => Boolean(customUiCsp && customUiCspDirectives.some((directive) => customUiCsp[directive]?.length));
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
package/lib/openid.d.ts
CHANGED
|
@@ -122,6 +122,7 @@ export declare const idTokenClaims: Readonly<Record<UserScope, UserClaim[]>>;
|
|
|
122
122
|
* @see {@link userClaims} for all possible claims (used by userinfo endpoint).
|
|
123
123
|
*/
|
|
124
124
|
export declare const extendedIdTokenClaimsByScope: Readonly<Partial<Record<UserScope, ExtendedIdTokenClaim[]>>>;
|
|
125
|
+
export declare const protectedAppAdditionalScopes: readonly [UserScope.CustomData, UserScope.Identities, UserScope.Roles, UserScope.Organizations, UserScope.OrganizationRoles];
|
|
125
126
|
/**
|
|
126
127
|
* All possible claims for each scope, combining base ID token claims and extended claims.
|
|
127
128
|
*
|
package/lib/openid.js
CHANGED
|
@@ -194,6 +194,13 @@ export const extendedIdTokenClaimsByScope = Object.freeze({
|
|
|
194
194
|
[UserScope.Organizations]: ['organizations', 'organization_data'],
|
|
195
195
|
[UserScope.OrganizationRoles]: ['organization_roles'],
|
|
196
196
|
});
|
|
197
|
+
export const protectedAppAdditionalScopes = [
|
|
198
|
+
UserScope.CustomData,
|
|
199
|
+
UserScope.Identities,
|
|
200
|
+
UserScope.Roles,
|
|
201
|
+
UserScope.Organizations,
|
|
202
|
+
UserScope.OrganizationRoles,
|
|
203
|
+
];
|
|
197
204
|
/**
|
|
198
205
|
* All possible claims for each scope, combining base ID token claims and extended claims.
|
|
199
206
|
*
|