@manta-eu/lib 0.1.10 → 0.2.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/dist/custom-app.d.ts +52 -0
- package/dist/custom-app.d.ts.map +1 -0
- package/dist/custom-app.js +146 -0
- package/dist/custom-app.js.map +1 -0
- package/package.json +1 -1
- package/src/custom-app.ts +182 -0
- package/dist/external-app.d.ts +0 -3
- package/dist/external-app.d.ts.map +0 -1
- package/dist/external-app.js +0 -29
- package/dist/external-app.js.map +0 -1
- package/src/external-app.ts +0 -27
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export declare const customAppClientIdPrefix = "ext-";
|
|
2
|
+
export declare const customAppClientIdPattern: RegExp;
|
|
3
|
+
export declare const maxCustomAppClientIdLength = 64;
|
|
4
|
+
export declare function customAppClientIdProblem(clientId: unknown): string | undefined;
|
|
5
|
+
export declare function redirectUrlProblem(raw: string): string | undefined;
|
|
6
|
+
export declare const customAppUsersIdpAlias = "manta-custom-app-users";
|
|
7
|
+
export declare const customAppUsersPath = "/agents/custom-app-users";
|
|
8
|
+
export declare const customAppUserTokenPath = "/agents/custom-app-users/token";
|
|
9
|
+
export declare const customAppUserIdPattern: RegExp;
|
|
10
|
+
export declare const customAppUserFieldKeyPattern: RegExp;
|
|
11
|
+
export declare const maxCustomAppUserFields = 50;
|
|
12
|
+
export declare const maxCustomAppUserFieldLength = 1024;
|
|
13
|
+
export declare const maxCustomAppUserAgentFieldsLength = 4096;
|
|
14
|
+
export declare const maxCustomAppUserNameLength = 200;
|
|
15
|
+
export declare const maxCustomAppUserEmailLength = 320;
|
|
16
|
+
export declare const maxCustomAppUserTeams = 50;
|
|
17
|
+
export declare const maxCustomAppUserDatastores = 50;
|
|
18
|
+
export type CustomAppUserFieldValue = string | number | boolean;
|
|
19
|
+
export type CustomAppUserField = {
|
|
20
|
+
value: CustomAppUserFieldValue;
|
|
21
|
+
agent?: boolean;
|
|
22
|
+
};
|
|
23
|
+
export type CustomAppUserProfile = {
|
|
24
|
+
name?: string;
|
|
25
|
+
email?: string;
|
|
26
|
+
fields?: Record<string, CustomAppUserField>;
|
|
27
|
+
};
|
|
28
|
+
export type CustomAppUserTokenRequest = CustomAppUserProfile & {
|
|
29
|
+
client_id: string;
|
|
30
|
+
client_secret: string;
|
|
31
|
+
user_id?: string;
|
|
32
|
+
anonymous_id?: string;
|
|
33
|
+
datastores?: string[];
|
|
34
|
+
teams?: string[];
|
|
35
|
+
};
|
|
36
|
+
export declare function customAppUserTeamsProblem(teams: unknown): string | undefined;
|
|
37
|
+
export declare function customAppUserDatastoresProblem(datastores: unknown): string | undefined;
|
|
38
|
+
export type CustomAppUserTokenResponse = {
|
|
39
|
+
access_token: string;
|
|
40
|
+
token_type: "Bearer";
|
|
41
|
+
expires_in: number;
|
|
42
|
+
};
|
|
43
|
+
export declare function customAppUserSubject(clientId: string, userId: string): string;
|
|
44
|
+
export declare function customAppUserIdOfSubject(clientId: string, subject: string | undefined): string | undefined;
|
|
45
|
+
export declare function anonymousSubject(clientId: string): string;
|
|
46
|
+
export declare function customAppClientIdOfUsername(username: string | undefined): string | undefined;
|
|
47
|
+
export declare function customAppUsernamePrefix(clientId: string): string;
|
|
48
|
+
export declare function anonymousPrincipalId(clientId: string, anonymousId: string): string;
|
|
49
|
+
export declare const anonymousVisitorIdName = "An anonymous visitor id";
|
|
50
|
+
export declare function customAppUserIdProblem(userId: unknown, name?: string): string | undefined;
|
|
51
|
+
export declare function customAppUserProfileProblem(profile: CustomAppUserProfile): string | undefined;
|
|
52
|
+
//# sourceMappingURL=custom-app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-app.d.ts","sourceRoot":"","sources":["../src/custom-app.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { isPresent } from "./present.js";
|
|
2
|
+
export const customAppClientIdPrefix = "ext-";
|
|
3
|
+
// As the plugin's own pattern: anything else names no app, and each name would get its own rate limiter.
|
|
4
|
+
export const customAppClientIdPattern = /^ext-[a-z0-9-]+$/;
|
|
5
|
+
export const maxCustomAppClientIdLength = 64;
|
|
6
|
+
export function customAppClientIdProblem(clientId) {
|
|
7
|
+
if (typeof clientId !== "string" ||
|
|
8
|
+
clientId.length > maxCustomAppClientIdLength ||
|
|
9
|
+
!customAppClientIdPattern.test(clientId)) {
|
|
10
|
+
return `${JSON.stringify(clientId)} is not a custom app's client ID, ext-...`;
|
|
11
|
+
}
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
const loopbackHosts = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
15
|
+
const maxRedirectUrlLength = 2048;
|
|
16
|
+
// Keycloak compares a redirect URL with what the app sends as an exact string, so it is stored as typed.
|
|
17
|
+
export function redirectUrlProblem(raw) {
|
|
18
|
+
if (raw.length > maxRedirectUrlLength)
|
|
19
|
+
return `Keep it under ${maxRedirectUrlLength} characters`;
|
|
20
|
+
if (raw.includes("*"))
|
|
21
|
+
return "Wildcards are not allowed";
|
|
22
|
+
if (/[\s,]/.test(raw))
|
|
23
|
+
return "Leave out commas and spaces, one URL per field";
|
|
24
|
+
let url;
|
|
25
|
+
try {
|
|
26
|
+
url = new URL(raw);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return "Enter a full URL, like https://app.example.com/callback";
|
|
30
|
+
}
|
|
31
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopbackHosts.has(url.hostname))) {
|
|
32
|
+
return "Use https, or http for localhost";
|
|
33
|
+
}
|
|
34
|
+
if (isPresent(url.username) || isPresent(url.password))
|
|
35
|
+
return "Leave out the username and password";
|
|
36
|
+
if (isPresent(url.hash))
|
|
37
|
+
return "Leave out the #fragment";
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
export const customAppUsersIdpAlias = "manta-custom-app-users";
|
|
41
|
+
export const customAppUsersPath = "/agents/custom-app-users";
|
|
42
|
+
export const customAppUserTokenPath = `${customAppUsersPath}/token`;
|
|
43
|
+
export const customAppUserIdPattern = /^[A-Za-z0-9_.@|-]{1,128}$/;
|
|
44
|
+
export const customAppUserFieldKeyPattern = /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/;
|
|
45
|
+
export const maxCustomAppUserFields = 50;
|
|
46
|
+
export const maxCustomAppUserFieldLength = 1024;
|
|
47
|
+
// The fields marked for the agent ride in every access token, so they are held well under a request header's size.
|
|
48
|
+
export const maxCustomAppUserAgentFieldsLength = 4096;
|
|
49
|
+
export const maxCustomAppUserNameLength = 200;
|
|
50
|
+
export const maxCustomAppUserEmailLength = 320;
|
|
51
|
+
export const maxCustomAppUserTeams = 50;
|
|
52
|
+
export const maxCustomAppUserDatastores = 50;
|
|
53
|
+
const maxTeamPathLength = 255;
|
|
54
|
+
export function customAppUserTeamsProblem(teams) {
|
|
55
|
+
if (teams === undefined)
|
|
56
|
+
return undefined;
|
|
57
|
+
if (!Array.isArray(teams) || teams.length > maxCustomAppUserTeams)
|
|
58
|
+
return `teams lists at most ${maxCustomAppUserTeams} teams`;
|
|
59
|
+
const bad = teams.find((team) => typeof team !== "string" || !team.startsWith("/") || team.length < 2 || team.length > maxTeamPathLength);
|
|
60
|
+
return bad === undefined ? undefined : `A team is its path, like /Sales/EU; ${JSON.stringify(bad)} is not one`;
|
|
61
|
+
}
|
|
62
|
+
export function customAppUserDatastoresProblem(datastores) {
|
|
63
|
+
if (datastores === undefined)
|
|
64
|
+
return undefined;
|
|
65
|
+
if (!Array.isArray(datastores) || !isPresent(datastores) || datastores.length > maxCustomAppUserDatastores) {
|
|
66
|
+
return `datastores lists 1 to ${maxCustomAppUserDatastores} datastores, or is left out`;
|
|
67
|
+
}
|
|
68
|
+
const bad = datastores.find((datastore) => typeof datastore !== "string" || !isPresent(datastore));
|
|
69
|
+
return bad === undefined ? undefined : `A datastore is its id; ${JSON.stringify(bad)} is not one`;
|
|
70
|
+
}
|
|
71
|
+
export function customAppUserSubject(clientId, userId) {
|
|
72
|
+
return `app:${clientId}:${userId}`;
|
|
73
|
+
}
|
|
74
|
+
export function customAppUserIdOfSubject(clientId, subject) {
|
|
75
|
+
const prefix = customAppUserSubject(clientId, "");
|
|
76
|
+
return subject?.startsWith(prefix) === true && subject.length > prefix.length
|
|
77
|
+
? subject.slice(prefix.length)
|
|
78
|
+
: undefined;
|
|
79
|
+
}
|
|
80
|
+
// Every anonymous visitor of one app shares this account; the visitor id rides in the token instead.
|
|
81
|
+
export function anonymousSubject(clientId) {
|
|
82
|
+
return `anon:${clientId}`;
|
|
83
|
+
}
|
|
84
|
+
// Only accounts the Worker made carry app.<client id>.<hash>; an upgrade renames the account to its email.
|
|
85
|
+
const generatedCustomAppUsername = /^app\.(ext-[a-z0-9-]+)\.([0-9a-f]{32}|anonymous)$/;
|
|
86
|
+
export function customAppClientIdOfUsername(username) {
|
|
87
|
+
return generatedCustomAppUsername.exec(username ?? "")?.[1];
|
|
88
|
+
}
|
|
89
|
+
export function customAppUsernamePrefix(clientId) {
|
|
90
|
+
return `app.${clientId}.`;
|
|
91
|
+
}
|
|
92
|
+
export function anonymousPrincipalId(clientId, anonymousId) {
|
|
93
|
+
return `anon:${clientId}:${anonymousId}`;
|
|
94
|
+
}
|
|
95
|
+
export const anonymousVisitorIdName = "An anonymous visitor id";
|
|
96
|
+
export function customAppUserIdProblem(userId, name = "A custom app user id") {
|
|
97
|
+
if (typeof userId !== "string" || !customAppUserIdPattern.test(userId)) {
|
|
98
|
+
return `${name} is 1 to 128 letters, digits or - _ . @ |`;
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
function fieldProblem(key, field) {
|
|
103
|
+
if (!customAppUserFieldKeyPattern.test(key))
|
|
104
|
+
return `Field name "${key}" must start with a letter and use letters, digits, - _ .`;
|
|
105
|
+
if (typeof field !== "object" || field === null || !("value" in field))
|
|
106
|
+
return `Field "${key}" needs a value`;
|
|
107
|
+
const { value, agent } = field;
|
|
108
|
+
if (!["string", "number", "boolean"].includes(typeof value))
|
|
109
|
+
return `Field "${key}" must be a string, number or boolean`;
|
|
110
|
+
if (typeof value === "string" && value.length > maxCustomAppUserFieldLength) {
|
|
111
|
+
return `Field "${key}" is longer than ${maxCustomAppUserFieldLength} characters`;
|
|
112
|
+
}
|
|
113
|
+
if (agent !== undefined && typeof agent !== "boolean")
|
|
114
|
+
return `Field "${key}" marks agent with true or false`;
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
export function customAppUserProfileProblem(profile) {
|
|
118
|
+
const { name, email, fields } = profile;
|
|
119
|
+
if (name !== undefined && (typeof name !== "string" || name.length > maxCustomAppUserNameLength)) {
|
|
120
|
+
return `A name is text of at most ${maxCustomAppUserNameLength} characters`;
|
|
121
|
+
}
|
|
122
|
+
if (email !== undefined &&
|
|
123
|
+
(typeof email !== "string" || email.length > maxCustomAppUserEmailLength || !email.includes("@"))) {
|
|
124
|
+
return "An email needs an @ and at most 320 characters";
|
|
125
|
+
}
|
|
126
|
+
if (fields === undefined)
|
|
127
|
+
return undefined;
|
|
128
|
+
if (typeof fields !== "object" || fields === null || Array.isArray(fields))
|
|
129
|
+
return "Fields are an object of named fields";
|
|
130
|
+
const entries = Object.entries(fields);
|
|
131
|
+
if (entries.length > maxCustomAppUserFields)
|
|
132
|
+
return `At most ${maxCustomAppUserFields} fields`;
|
|
133
|
+
for (const [key, field] of entries) {
|
|
134
|
+
const problem = fieldProblem(key, field);
|
|
135
|
+
if (problem !== undefined)
|
|
136
|
+
return problem;
|
|
137
|
+
}
|
|
138
|
+
const agentFieldsLength = entries.reduce((length, [key, field]) => field.agent === true
|
|
139
|
+
? length + key.length + String(field.value).length
|
|
140
|
+
: length, 0);
|
|
141
|
+
if (agentFieldsLength > maxCustomAppUserAgentFieldsLength) {
|
|
142
|
+
return `The fields marked for the agent come to at most ${maxCustomAppUserAgentFieldsLength} characters, names included`;
|
|
143
|
+
}
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=custom-app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-app.js","sourceRoot":"","sources":["../src/custom-app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAC9C,yGAAyG;AACzG,MAAM,CAAC,MAAM,wBAAwB,GAAG,kBAAkB,CAAC;AAC3D,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAE7C,MAAM,UAAU,wBAAwB,CAAC,QAAiB;IACxD,IACE,OAAO,QAAQ,KAAK,QAAQ;QAC5B,QAAQ,CAAC,MAAM,GAAG,0BAA0B;QAC5C,CAAC,wBAAwB,CAAC,IAAI,CAAC,QAAQ,CAAC,EACxC,CAAC;QACD,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,2CAA2C,CAAC;IAChF,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;AACnE,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAElC,yGAAyG;AACzG,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,IAAI,GAAG,CAAC,MAAM,GAAG,oBAAoB;QAAE,OAAO,iBAAiB,oBAAoB,aAAa,CAAC;IACjG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,2BAA2B,CAAC;IAC1D,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,gDAAgD,CAAC;IAE/E,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,yDAAyD,CAAC;IACnE,CAAC;IAED,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QAChG,OAAO,kCAAkC,CAAC;IAC5C,CAAC;IACD,IAAI,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,qCAAqC,CAAC;IACrG,IAAI,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,yBAAyB,CAAC;IAC1D,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAG,wBAAwB,CAAC;AAC/D,MAAM,CAAC,MAAM,kBAAkB,GAAG,0BAA0B,CAAC;AAC7D,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,kBAAkB,QAAQ,CAAC;AACpE,MAAM,CAAC,MAAM,sBAAsB,GAAG,2BAA2B,CAAC;AAClE,MAAM,CAAC,MAAM,4BAA4B,GAAG,gCAAgC,CAAC;AAC7E,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AACzC,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,CAAC;AAChD,mHAAmH;AACnH,MAAM,CAAC,MAAM,iCAAiC,GAAG,IAAI,CAAC;AACtD,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAG,CAAC;AAC9C,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAC;AAC/C,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC;AACxC,MAAM,CAAC,MAAM,0BAA0B,GAAG,EAAE,CAAC;AAC7C,MAAM,iBAAiB,GAAG,GAAG,CAAC;AAoB9B,MAAM,UAAU,yBAAyB,CAAC,KAAc;IACtD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,qBAAqB;QAC/D,OAAO,uBAAuB,qBAAqB,QAAQ,CAAC;IAC9D,MAAM,GAAG,GAAG,KAAK,CAAC,IAAI,CACpB,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,iBAAiB,CAClH,CAAC;IACF,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,uCAAuC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,aAAa,CAAC;AACjH,CAAC;AAED,MAAM,UAAU,8BAA8B,CAAC,UAAmB;IAChE,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC/C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC,MAAM,GAAG,0BAA0B,EAAE,CAAC;QAC3G,OAAO,yBAAyB,0BAA0B,6BAA6B,CAAC;IAC1F,CAAC;IACD,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC;IACnG,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,0BAA0B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,aAAa,CAAC;AACpG,CAAC;AAID,MAAM,UAAU,oBAAoB,CAAC,QAAgB,EAAE,MAAc;IACnE,OAAO,OAAO,QAAQ,IAAI,MAAM,EAAE,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,QAAgB,EAAE,OAA2B;IACpF,MAAM,MAAM,GAAG,oBAAoB,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAClD,OAAO,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM;QAC3E,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;QAC9B,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,OAAO,QAAQ,QAAQ,EAAE,CAAC;AAC5B,CAAC;AAED,2GAA2G;AAC3G,MAAM,0BAA0B,GAAG,mDAAmD,CAAC;AAEvF,MAAM,UAAU,2BAA2B,CAAC,QAA4B;IACtE,OAAO,0BAA0B,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,QAAgB;IACtD,OAAO,OAAO,QAAQ,GAAG,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,QAAgB,EAAE,WAAmB;IACxE,OAAO,QAAQ,QAAQ,IAAI,WAAW,EAAE,CAAC;AAC3C,CAAC;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAG,yBAAyB,CAAC;AAEhE,MAAM,UAAU,sBAAsB,CAAC,MAAe,EAAE,IAAI,GAAG,sBAAsB;IACnF,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,sBAAsB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACvE,OAAO,GAAG,IAAI,2CAA2C,CAAC;IAC5D,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,YAAY,CAAC,GAAW,EAAE,KAAc;IAC/C,IAAI,CAAC,4BAA4B,CAAC,IAAI,CAAC,GAAG,CAAC;QACzC,OAAO,eAAe,GAAG,2DAA2D,CAAC;IACvF,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,CAAC,OAAO,IAAI,KAAK,CAAC;QAAE,OAAO,UAAU,GAAG,iBAAiB,CAAC;IAC9G,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,KAA4C,CAAC;IACtE,IAAI,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,OAAO,KAAK,CAAC;QACzD,OAAO,UAAU,GAAG,uCAAuC,CAAC;IAC9D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,2BAA2B,EAAE,CAAC;QAC5E,OAAO,UAAU,GAAG,oBAAoB,2BAA2B,aAAa,CAAC;IACnF,CAAC;IACD,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,UAAU,GAAG,kCAAkC,CAAC;IAC9G,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,OAA6B;IACvE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IACxC,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,0BAA0B,CAAC,EAAE,CAAC;QACjG,OAAO,6BAA6B,0BAA0B,aAAa,CAAC;IAC9E,CAAC;IACD,IACE,KAAK,KAAK,SAAS;QACnB,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,2BAA2B,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EACjG,CAAC;QACD,OAAO,gDAAgD,CAAC;IAC1D,CAAC;IACD,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC3C,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QACxE,OAAO,sCAAsC,CAAC;IAChD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,OAAO,CAAC,MAAM,GAAG,sBAAsB;QAAE,OAAO,WAAW,sBAAsB,SAAS,CAAC;IAC/F,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACzC,IAAI,OAAO,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC;IAC5C,CAAC;IACD,MAAM,iBAAiB,GAAG,OAAO,CAAC,MAAM,CACtC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CACtB,KAA6B,CAAC,KAAK,KAAK,IAAI;QAC3C,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,MAAM,CAAE,KAA4B,CAAC,KAAK,CAAC,CAAC,MAAM;QAC1E,CAAC,CAAC,MAAM,EACZ,CAAC,CACF,CAAC;IACF,IAAI,iBAAiB,GAAG,iCAAiC,EAAE,CAAC;QAC1D,OAAO,mDAAmD,iCAAiC,6BAA6B,CAAC;IAC3H,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { isPresent } from "./present";
|
|
2
|
+
|
|
3
|
+
export const customAppClientIdPrefix = "ext-";
|
|
4
|
+
// As the plugin's own pattern: anything else names no app, and each name would get its own rate limiter.
|
|
5
|
+
export const customAppClientIdPattern = /^ext-[a-z0-9-]+$/;
|
|
6
|
+
export const maxCustomAppClientIdLength = 64;
|
|
7
|
+
|
|
8
|
+
export function customAppClientIdProblem(clientId: unknown): string | undefined {
|
|
9
|
+
if (
|
|
10
|
+
typeof clientId !== "string" ||
|
|
11
|
+
clientId.length > maxCustomAppClientIdLength ||
|
|
12
|
+
!customAppClientIdPattern.test(clientId)
|
|
13
|
+
) {
|
|
14
|
+
return `${JSON.stringify(clientId)} is not a custom app's client ID, ext-...`;
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const loopbackHosts = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
20
|
+
const maxRedirectUrlLength = 2048;
|
|
21
|
+
|
|
22
|
+
// Keycloak compares a redirect URL with what the app sends as an exact string, so it is stored as typed.
|
|
23
|
+
export function redirectUrlProblem(raw: string): string | undefined {
|
|
24
|
+
if (raw.length > maxRedirectUrlLength) return `Keep it under ${maxRedirectUrlLength} characters`;
|
|
25
|
+
if (raw.includes("*")) return "Wildcards are not allowed";
|
|
26
|
+
if (/[\s,]/.test(raw)) return "Leave out commas and spaces, one URL per field";
|
|
27
|
+
|
|
28
|
+
let url: URL;
|
|
29
|
+
try {
|
|
30
|
+
url = new URL(raw);
|
|
31
|
+
} catch {
|
|
32
|
+
return "Enter a full URL, like https://app.example.com/callback";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopbackHosts.has(url.hostname))) {
|
|
36
|
+
return "Use https, or http for localhost";
|
|
37
|
+
}
|
|
38
|
+
if (isPresent(url.username) || isPresent(url.password)) return "Leave out the username and password";
|
|
39
|
+
if (isPresent(url.hash)) return "Leave out the #fragment";
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const customAppUsersIdpAlias = "manta-custom-app-users";
|
|
44
|
+
export const customAppUsersPath = "/agents/custom-app-users";
|
|
45
|
+
export const customAppUserTokenPath = `${customAppUsersPath}/token`;
|
|
46
|
+
export const customAppUserIdPattern = /^[A-Za-z0-9_.@|-]{1,128}$/;
|
|
47
|
+
export const customAppUserFieldKeyPattern = /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/;
|
|
48
|
+
export const maxCustomAppUserFields = 50;
|
|
49
|
+
export const maxCustomAppUserFieldLength = 1024;
|
|
50
|
+
// The fields marked for the agent ride in every access token, so they are held well under a request header's size.
|
|
51
|
+
export const maxCustomAppUserAgentFieldsLength = 4096;
|
|
52
|
+
export const maxCustomAppUserNameLength = 200;
|
|
53
|
+
export const maxCustomAppUserEmailLength = 320;
|
|
54
|
+
export const maxCustomAppUserTeams = 50;
|
|
55
|
+
export const maxCustomAppUserDatastores = 50;
|
|
56
|
+
const maxTeamPathLength = 255;
|
|
57
|
+
|
|
58
|
+
export type CustomAppUserFieldValue = string | number | boolean;
|
|
59
|
+
export type CustomAppUserField = { value: CustomAppUserFieldValue; agent?: boolean };
|
|
60
|
+
|
|
61
|
+
export type CustomAppUserProfile = {
|
|
62
|
+
name?: string;
|
|
63
|
+
email?: string;
|
|
64
|
+
fields?: Record<string, CustomAppUserField>;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type CustomAppUserTokenRequest = CustomAppUserProfile & {
|
|
68
|
+
client_id: string;
|
|
69
|
+
client_secret: string;
|
|
70
|
+
user_id?: string;
|
|
71
|
+
anonymous_id?: string;
|
|
72
|
+
datastores?: string[];
|
|
73
|
+
teams?: string[];
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export function customAppUserTeamsProblem(teams: unknown): string | undefined {
|
|
77
|
+
if (teams === undefined) return undefined;
|
|
78
|
+
if (!Array.isArray(teams) || teams.length > maxCustomAppUserTeams)
|
|
79
|
+
return `teams lists at most ${maxCustomAppUserTeams} teams`;
|
|
80
|
+
const bad = teams.find(
|
|
81
|
+
(team) => typeof team !== "string" || !team.startsWith("/") || team.length < 2 || team.length > maxTeamPathLength,
|
|
82
|
+
);
|
|
83
|
+
return bad === undefined ? undefined : `A team is its path, like /Sales/EU; ${JSON.stringify(bad)} is not one`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function customAppUserDatastoresProblem(datastores: unknown): string | undefined {
|
|
87
|
+
if (datastores === undefined) return undefined;
|
|
88
|
+
if (!Array.isArray(datastores) || !isPresent(datastores) || datastores.length > maxCustomAppUserDatastores) {
|
|
89
|
+
return `datastores lists 1 to ${maxCustomAppUserDatastores} datastores, or is left out`;
|
|
90
|
+
}
|
|
91
|
+
const bad = datastores.find((datastore) => typeof datastore !== "string" || !isPresent(datastore));
|
|
92
|
+
return bad === undefined ? undefined : `A datastore is its id; ${JSON.stringify(bad)} is not one`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type CustomAppUserTokenResponse = { access_token: string; token_type: "Bearer"; expires_in: number };
|
|
96
|
+
|
|
97
|
+
export function customAppUserSubject(clientId: string, userId: string): string {
|
|
98
|
+
return `app:${clientId}:${userId}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function customAppUserIdOfSubject(clientId: string, subject: string | undefined): string | undefined {
|
|
102
|
+
const prefix = customAppUserSubject(clientId, "");
|
|
103
|
+
return subject?.startsWith(prefix) === true && subject.length > prefix.length
|
|
104
|
+
? subject.slice(prefix.length)
|
|
105
|
+
: undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Every anonymous visitor of one app shares this account; the visitor id rides in the token instead.
|
|
109
|
+
export function anonymousSubject(clientId: string): string {
|
|
110
|
+
return `anon:${clientId}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Only accounts the Worker made carry app.<client id>.<hash>; an upgrade renames the account to its email.
|
|
114
|
+
const generatedCustomAppUsername = /^app\.(ext-[a-z0-9-]+)\.([0-9a-f]{32}|anonymous)$/;
|
|
115
|
+
|
|
116
|
+
export function customAppClientIdOfUsername(username: string | undefined): string | undefined {
|
|
117
|
+
return generatedCustomAppUsername.exec(username ?? "")?.[1];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function customAppUsernamePrefix(clientId: string): string {
|
|
121
|
+
return `app.${clientId}.`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function anonymousPrincipalId(clientId: string, anonymousId: string): string {
|
|
125
|
+
return `anon:${clientId}:${anonymousId}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const anonymousVisitorIdName = "An anonymous visitor id";
|
|
129
|
+
|
|
130
|
+
export function customAppUserIdProblem(userId: unknown, name = "A custom app user id"): string | undefined {
|
|
131
|
+
if (typeof userId !== "string" || !customAppUserIdPattern.test(userId)) {
|
|
132
|
+
return `${name} is 1 to 128 letters, digits or - _ . @ |`;
|
|
133
|
+
}
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function fieldProblem(key: string, field: unknown): string | undefined {
|
|
138
|
+
if (!customAppUserFieldKeyPattern.test(key))
|
|
139
|
+
return `Field name "${key}" must start with a letter and use letters, digits, - _ .`;
|
|
140
|
+
if (typeof field !== "object" || field === null || !("value" in field)) return `Field "${key}" needs a value`;
|
|
141
|
+
const { value, agent } = field as { value: unknown; agent?: unknown };
|
|
142
|
+
if (!["string", "number", "boolean"].includes(typeof value))
|
|
143
|
+
return `Field "${key}" must be a string, number or boolean`;
|
|
144
|
+
if (typeof value === "string" && value.length > maxCustomAppUserFieldLength) {
|
|
145
|
+
return `Field "${key}" is longer than ${maxCustomAppUserFieldLength} characters`;
|
|
146
|
+
}
|
|
147
|
+
if (agent !== undefined && typeof agent !== "boolean") return `Field "${key}" marks agent with true or false`;
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function customAppUserProfileProblem(profile: CustomAppUserProfile): string | undefined {
|
|
152
|
+
const { name, email, fields } = profile;
|
|
153
|
+
if (name !== undefined && (typeof name !== "string" || name.length > maxCustomAppUserNameLength)) {
|
|
154
|
+
return `A name is text of at most ${maxCustomAppUserNameLength} characters`;
|
|
155
|
+
}
|
|
156
|
+
if (
|
|
157
|
+
email !== undefined &&
|
|
158
|
+
(typeof email !== "string" || email.length > maxCustomAppUserEmailLength || !email.includes("@"))
|
|
159
|
+
) {
|
|
160
|
+
return "An email needs an @ and at most 320 characters";
|
|
161
|
+
}
|
|
162
|
+
if (fields === undefined) return undefined;
|
|
163
|
+
if (typeof fields !== "object" || fields === null || Array.isArray(fields))
|
|
164
|
+
return "Fields are an object of named fields";
|
|
165
|
+
const entries = Object.entries(fields);
|
|
166
|
+
if (entries.length > maxCustomAppUserFields) return `At most ${maxCustomAppUserFields} fields`;
|
|
167
|
+
for (const [key, field] of entries) {
|
|
168
|
+
const problem = fieldProblem(key, field);
|
|
169
|
+
if (problem !== undefined) return problem;
|
|
170
|
+
}
|
|
171
|
+
const agentFieldsLength = entries.reduce(
|
|
172
|
+
(length, [key, field]) =>
|
|
173
|
+
(field as { agent?: boolean }).agent === true
|
|
174
|
+
? length + key.length + String((field as { value: unknown }).value).length
|
|
175
|
+
: length,
|
|
176
|
+
0,
|
|
177
|
+
);
|
|
178
|
+
if (agentFieldsLength > maxCustomAppUserAgentFieldsLength) {
|
|
179
|
+
return `The fields marked for the agent come to at most ${maxCustomAppUserAgentFieldsLength} characters, names included`;
|
|
180
|
+
}
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
package/dist/external-app.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"external-app.d.ts","sourceRoot":"","sources":["../src/external-app.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,yBAAyB,SAAS,CAAC;AAMhD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAkBlE"}
|
package/dist/external-app.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { isPresent } from "./present.js";
|
|
2
|
-
export const externalAppClientIdPrefix = "ext-";
|
|
3
|
-
const loopbackHosts = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
4
|
-
const maxRedirectUrlLength = 2048;
|
|
5
|
-
// Keycloak compares a redirect URL with what the app sends as an exact string, so it is stored as typed.
|
|
6
|
-
export function redirectUrlProblem(raw) {
|
|
7
|
-
if (raw.length > maxRedirectUrlLength)
|
|
8
|
-
return `Keep it under ${maxRedirectUrlLength} characters`;
|
|
9
|
-
if (raw.includes("*"))
|
|
10
|
-
return "Wildcards are not allowed";
|
|
11
|
-
if (/[\s,]/.test(raw))
|
|
12
|
-
return "Leave out commas and spaces, one URL per field";
|
|
13
|
-
let url;
|
|
14
|
-
try {
|
|
15
|
-
url = new URL(raw);
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
return "Enter a full URL, like https://app.example.com/callback";
|
|
19
|
-
}
|
|
20
|
-
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopbackHosts.has(url.hostname))) {
|
|
21
|
-
return "Use https, or http for localhost";
|
|
22
|
-
}
|
|
23
|
-
if (isPresent(url.username) || isPresent(url.password))
|
|
24
|
-
return "Leave out the username and password";
|
|
25
|
-
if (isPresent(url.hash))
|
|
26
|
-
return "Leave out the #fragment";
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
//# sourceMappingURL=external-app.js.map
|
package/dist/external-app.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"external-app.js","sourceRoot":"","sources":["../src/external-app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC;AAEhD,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;AACnE,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAElC,yGAAyG;AACzG,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,IAAI,GAAG,CAAC,MAAM,GAAG,oBAAoB;QAAE,OAAO,iBAAiB,oBAAoB,aAAa,CAAC;IACjG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,2BAA2B,CAAC;IAC1D,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,gDAAgD,CAAC;IAE/E,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,yDAAyD,CAAC;IACnE,CAAC;IAED,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,aAAa,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QAChG,OAAO,kCAAkC,CAAC;IAC5C,CAAC;IACD,IAAI,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,qCAAqC,CAAC;IACrG,IAAI,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,yBAAyB,CAAC;IAC1D,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
package/src/external-app.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { isPresent } from "./present";
|
|
2
|
-
|
|
3
|
-
export const externalAppClientIdPrefix = "ext-";
|
|
4
|
-
|
|
5
|
-
const loopbackHosts = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
6
|
-
const maxRedirectUrlLength = 2048;
|
|
7
|
-
|
|
8
|
-
// Keycloak compares a redirect URL with what the app sends as an exact string, so it is stored as typed.
|
|
9
|
-
export function redirectUrlProblem(raw: string): string | undefined {
|
|
10
|
-
if (raw.length > maxRedirectUrlLength) return `Keep it under ${maxRedirectUrlLength} characters`;
|
|
11
|
-
if (raw.includes("*")) return "Wildcards are not allowed";
|
|
12
|
-
if (/[\s,]/.test(raw)) return "Leave out commas and spaces, one URL per field";
|
|
13
|
-
|
|
14
|
-
let url: URL;
|
|
15
|
-
try {
|
|
16
|
-
url = new URL(raw);
|
|
17
|
-
} catch {
|
|
18
|
-
return "Enter a full URL, like https://app.example.com/callback";
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopbackHosts.has(url.hostname))) {
|
|
22
|
-
return "Use https, or http for localhost";
|
|
23
|
-
}
|
|
24
|
-
if (isPresent(url.username) || isPresent(url.password)) return "Leave out the username and password";
|
|
25
|
-
if (isPresent(url.hash)) return "Leave out the #fragment";
|
|
26
|
-
return undefined;
|
|
27
|
-
}
|