@better-auth/sso 1.5.0-beta.1 → 1.5.0-beta.10
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/.turbo/turbo-build.log +13 -9
- package/LICENSE.md +15 -12
- package/dist/client.d.mts +7 -2
- package/dist/client.mjs +7 -2
- package/dist/client.mjs.map +1 -0
- package/dist/{index-CvpS40sl.d.mts → index-CBBJTszO.d.mts} +429 -19
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1107 -489
- package/dist/index.mjs.map +1 -0
- package/package.json +17 -14
- package/src/client.ts +5 -1
- package/src/constants.ts +16 -0
- package/src/index.ts +55 -6
- package/src/linking/org-assignment.test.ts +1 -1
- package/src/linking/org-assignment.ts +20 -13
- package/src/oidc.test.ts +113 -1
- package/src/providers.test.ts +1326 -0
- package/src/routes/providers.ts +565 -0
- package/src/routes/schemas.ts +96 -0
- package/src/routes/sso.ts +285 -65
- package/src/saml/algorithms.ts +1 -31
- package/src/saml/assertions.test.ts +239 -0
- package/src/saml/assertions.ts +62 -0
- package/src/saml/index.ts +2 -0
- package/src/saml/parser.ts +56 -0
- package/src/saml-state.ts +78 -0
- package/src/saml.test.ts +2133 -422
- package/src/types.ts +20 -0
- package/src/utils.test.ts +103 -0
- package/src/utils.ts +45 -5
- package/tsconfig.json +3 -0
- package/tsdown.config.ts +1 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-auth/sso",
|
|
3
3
|
"author": "Bereket Engida",
|
|
4
|
-
"version": "1.5.0-beta.
|
|
4
|
+
"version": "1.5.0-beta.10",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.mts",
|
|
@@ -52,29 +52,32 @@
|
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@better-auth/utils": "0.3.
|
|
55
|
+
"@better-auth/utils": "0.3.1",
|
|
56
56
|
"@better-fetch/fetch": "1.1.21",
|
|
57
|
-
"fast-xml-parser": "^5.
|
|
57
|
+
"fast-xml-parser": "^5.3.3",
|
|
58
58
|
"jose": "^6.1.0",
|
|
59
|
-
"samlify": "^2.10.
|
|
60
|
-
"zod": "^4.
|
|
59
|
+
"samlify": "^2.10.2",
|
|
60
|
+
"zod": "^4.3.6"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/body-parser": "^1.19.6",
|
|
64
|
-
"@types/express": "^5.0.
|
|
65
|
-
"better-call": "1.
|
|
66
|
-
"body-parser": "^2.2.
|
|
67
|
-
"express": "^5.1
|
|
68
|
-
"oauth2-mock-server": "^8.2.
|
|
69
|
-
"tsdown": "^0.
|
|
70
|
-
"better-auth": "1.5.0-beta.
|
|
64
|
+
"@types/express": "^5.0.6",
|
|
65
|
+
"better-call": "1.2.0",
|
|
66
|
+
"body-parser": "^2.2.2",
|
|
67
|
+
"express": "^5.2.1",
|
|
68
|
+
"oauth2-mock-server": "^8.2.1",
|
|
69
|
+
"tsdown": "^0.20.1",
|
|
70
|
+
"@better-auth/core": "1.5.0-beta.10",
|
|
71
|
+
"better-auth": "1.5.0-beta.10"
|
|
71
72
|
},
|
|
72
73
|
"peerDependencies": {
|
|
73
|
-
"better-auth": "
|
|
74
|
+
"@better-auth/utils": "0.3.1",
|
|
75
|
+
"@better-auth/core": "1.5.0-beta.10",
|
|
76
|
+
"better-auth": "1.5.0-beta.10"
|
|
74
77
|
},
|
|
75
78
|
"scripts": {
|
|
76
79
|
"test": "vitest",
|
|
77
|
-
"coverage": "vitest run --coverage",
|
|
80
|
+
"coverage": "vitest run --coverage --coverage.provider=istanbul",
|
|
78
81
|
"lint:package": "publint run --strict",
|
|
79
82
|
"lint:types": "attw --profile esm-only --pack .",
|
|
80
83
|
"build": "tsdown",
|
package/src/client.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BetterAuthClientPlugin } from "better-auth";
|
|
1
|
+
import type { BetterAuthClientPlugin } from "better-auth/client";
|
|
2
2
|
import type { SSOPlugin } from "./index";
|
|
3
3
|
|
|
4
4
|
interface SSOClientOptions {
|
|
@@ -21,5 +21,9 @@ export const ssoClient = <CO extends SSOClientOptions>(
|
|
|
21
21
|
: false;
|
|
22
22
|
};
|
|
23
23
|
}>,
|
|
24
|
+
pathMethods: {
|
|
25
|
+
"/sso/providers": "GET",
|
|
26
|
+
"/sso/providers/:providerId": "GET",
|
|
27
|
+
},
|
|
24
28
|
} satisfies BetterAuthClientPlugin;
|
|
25
29
|
};
|
package/src/constants.ts
CHANGED
|
@@ -40,3 +40,19 @@ export const DEFAULT_ASSERTION_TTL_MS = 15 * 60 * 1000;
|
|
|
40
40
|
* - Distributed systems across timezones
|
|
41
41
|
*/
|
|
42
42
|
export const DEFAULT_CLOCK_SKEW_MS = 5 * 60 * 1000;
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Size Limits (DoS Protection)
|
|
46
|
+
// ============================================================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Default maximum size for SAML responses (256 KB).
|
|
50
|
+
* Protects against memory exhaustion from oversized SAML payloads.
|
|
51
|
+
*/
|
|
52
|
+
export const DEFAULT_MAX_SAML_RESPONSE_SIZE = 256 * 1024;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Default maximum size for IdP metadata (100 KB).
|
|
56
|
+
* Protects against oversized metadata documents.
|
|
57
|
+
*/
|
|
58
|
+
export const DEFAULT_MAX_SAML_METADATA_SIZE = 100 * 1024;
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,12 @@ import {
|
|
|
7
7
|
requestDomainVerification,
|
|
8
8
|
verifyDomain,
|
|
9
9
|
} from "./routes/domain-verification";
|
|
10
|
+
import {
|
|
11
|
+
deleteSSOProvider,
|
|
12
|
+
getSSOProvider,
|
|
13
|
+
listSSOProviders,
|
|
14
|
+
updateSSOProvider,
|
|
15
|
+
} from "./routes/providers";
|
|
10
16
|
import {
|
|
11
17
|
acsEndpoint,
|
|
12
18
|
callbackSSO,
|
|
@@ -16,6 +22,12 @@ import {
|
|
|
16
22
|
spMetadata,
|
|
17
23
|
} from "./routes/sso";
|
|
18
24
|
|
|
25
|
+
export {
|
|
26
|
+
DEFAULT_CLOCK_SKEW_MS,
|
|
27
|
+
DEFAULT_MAX_SAML_METADATA_SIZE,
|
|
28
|
+
DEFAULT_MAX_SAML_RESPONSE_SIZE,
|
|
29
|
+
} from "./constants";
|
|
30
|
+
|
|
19
31
|
export {
|
|
20
32
|
type SAMLConditions,
|
|
21
33
|
type TimestampValidationOptions,
|
|
@@ -35,6 +47,14 @@ import type { OIDCConfig, SAMLConfig, SSOOptions, SSOProvider } from "./types";
|
|
|
35
47
|
|
|
36
48
|
export type { SAMLConfig, OIDCConfig, SSOOptions, SSOProvider };
|
|
37
49
|
|
|
50
|
+
declare module "@better-auth/core" {
|
|
51
|
+
interface BetterAuthPluginRegistry<AuthOptions, Options> {
|
|
52
|
+
sso: {
|
|
53
|
+
creator: typeof sso;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
38
58
|
export {
|
|
39
59
|
computeDiscoveryUrl,
|
|
40
60
|
type DiscoverOIDCConfigParams,
|
|
@@ -78,6 +98,10 @@ type SSOEndpoints<O extends SSOOptions> = {
|
|
|
78
98
|
callbackSSO: ReturnType<typeof callbackSSO>;
|
|
79
99
|
callbackSSOSAML: ReturnType<typeof callbackSSOSAML>;
|
|
80
100
|
acsEndpoint: ReturnType<typeof acsEndpoint>;
|
|
101
|
+
listSSOProviders: ReturnType<typeof listSSOProviders>;
|
|
102
|
+
getSSOProvider: ReturnType<typeof getSSOProvider>;
|
|
103
|
+
updateSSOProvider: ReturnType<typeof updateSSOProvider>;
|
|
104
|
+
deleteSSOProvider: ReturnType<typeof deleteSSOProvider>;
|
|
81
105
|
};
|
|
82
106
|
|
|
83
107
|
export type SSOPlugin<O extends SSOOptions> = {
|
|
@@ -88,6 +112,16 @@ export type SSOPlugin<O extends SSOOptions> = {
|
|
|
88
112
|
: {});
|
|
89
113
|
};
|
|
90
114
|
|
|
115
|
+
/**
|
|
116
|
+
* SAML endpoint paths that should skip origin check validation.
|
|
117
|
+
* These endpoints receive POST requests from external Identity Providers,
|
|
118
|
+
* which won't have a matching Origin header.
|
|
119
|
+
*/
|
|
120
|
+
const SAML_SKIP_ORIGIN_CHECK_PATHS = [
|
|
121
|
+
"/sso/saml2/callback", // SP-initiated SSO callback (prefix matches /callback/:providerId)
|
|
122
|
+
"/sso/saml2/sp/acs", // IdP-initiated SSO ACS (prefix matches /sp/acs/:providerId)
|
|
123
|
+
];
|
|
124
|
+
|
|
91
125
|
export function sso<
|
|
92
126
|
O extends SSOOptions & {
|
|
93
127
|
domainVerification?: { enabled: true };
|
|
@@ -97,7 +131,7 @@ export function sso<
|
|
|
97
131
|
): {
|
|
98
132
|
id: "sso";
|
|
99
133
|
endpoints: SSOEndpoints<O> & DomainVerificationEndpoints;
|
|
100
|
-
schema:
|
|
134
|
+
schema: NonNullable<BetterAuthPlugin["schema"]>;
|
|
101
135
|
options: O;
|
|
102
136
|
};
|
|
103
137
|
export function sso<O extends SSOOptions>(
|
|
@@ -107,7 +141,9 @@ export function sso<O extends SSOOptions>(
|
|
|
107
141
|
endpoints: SSOEndpoints<O>;
|
|
108
142
|
};
|
|
109
143
|
|
|
110
|
-
export function sso<O extends SSOOptions>(
|
|
144
|
+
export function sso<O extends SSOOptions>(
|
|
145
|
+
options?: O | undefined,
|
|
146
|
+
): BetterAuthPlugin {
|
|
111
147
|
const optionsWithStore = options as O;
|
|
112
148
|
|
|
113
149
|
let endpoints = {
|
|
@@ -117,6 +153,10 @@ export function sso<O extends SSOOptions>(options?: O | undefined): any {
|
|
|
117
153
|
callbackSSO: callbackSSO(optionsWithStore),
|
|
118
154
|
callbackSSOSAML: callbackSSOSAML(optionsWithStore),
|
|
119
155
|
acsEndpoint: acsEndpoint(optionsWithStore),
|
|
156
|
+
listSSOProviders: listSSOProviders(),
|
|
157
|
+
getSSOProvider: getSSOProvider(),
|
|
158
|
+
updateSSOProvider: updateSSOProvider(optionsWithStore),
|
|
159
|
+
deleteSSOProvider: deleteSSOProvider(),
|
|
120
160
|
};
|
|
121
161
|
|
|
122
162
|
if (options?.domainVerification?.enabled) {
|
|
@@ -133,6 +173,18 @@ export function sso<O extends SSOOptions>(options?: O | undefined): any {
|
|
|
133
173
|
|
|
134
174
|
return {
|
|
135
175
|
id: "sso",
|
|
176
|
+
init(ctx) {
|
|
177
|
+
const existing = ctx.skipOriginCheck;
|
|
178
|
+
if (existing === true) {
|
|
179
|
+
return {};
|
|
180
|
+
}
|
|
181
|
+
const existingPaths = Array.isArray(existing) ? existing : [];
|
|
182
|
+
return {
|
|
183
|
+
context: {
|
|
184
|
+
skipOriginCheck: [...existingPaths, ...SAML_SKIP_ORIGIN_CHECK_PATHS],
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
},
|
|
136
188
|
endpoints,
|
|
137
189
|
hooks: {
|
|
138
190
|
after: [
|
|
@@ -146,10 +198,7 @@ export function sso<O extends SSOOptions>(options?: O | undefined): any {
|
|
|
146
198
|
return;
|
|
147
199
|
}
|
|
148
200
|
|
|
149
|
-
|
|
150
|
-
(plugin: { id: string }) => plugin.id === "organization",
|
|
151
|
-
);
|
|
152
|
-
if (!isOrgPluginEnabled) {
|
|
201
|
+
if (!ctx.context.hasPlugin("organization")) {
|
|
153
202
|
return;
|
|
154
203
|
}
|
|
155
204
|
|
|
@@ -56,7 +56,7 @@ describe("assignOrganizationByDomain", () => {
|
|
|
56
56
|
|
|
57
57
|
const createContext = async () => {
|
|
58
58
|
const context = await auth.$context;
|
|
59
|
-
return { context } as Partial<GenericEndpointContext>;
|
|
59
|
+
return { context } as unknown as Partial<GenericEndpointContext>;
|
|
60
60
|
};
|
|
61
61
|
|
|
62
62
|
return { auth, data, createContext };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { GenericEndpointContext, OAuth2Tokens, User } from "better-auth";
|
|
2
2
|
import type { SSOOptions, SSOProvider } from "../types";
|
|
3
|
+
import { domainMatches } from "../utils";
|
|
3
4
|
import type { NormalizedSSOProfile } from "./types";
|
|
4
5
|
|
|
5
6
|
export interface OrganizationProvisioningOptions {
|
|
@@ -39,11 +40,7 @@ export async function assignOrganizationFromProvider(
|
|
|
39
40
|
return;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
(plugin) => plugin.id === "organization",
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
if (!isOrgPluginEnabled) {
|
|
43
|
+
if (!ctx.context.hasPlugin("organization")) {
|
|
47
44
|
return;
|
|
48
45
|
}
|
|
49
46
|
|
|
@@ -105,11 +102,7 @@ export async function assignOrganizationByDomain(
|
|
|
105
102
|
return;
|
|
106
103
|
}
|
|
107
104
|
|
|
108
|
-
|
|
109
|
-
(plugin) => plugin.id === "organization",
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
if (!isOrgPluginEnabled) {
|
|
105
|
+
if (!ctx.context.hasPlugin("organization")) {
|
|
113
106
|
return;
|
|
114
107
|
}
|
|
115
108
|
|
|
@@ -118,6 +111,8 @@ export async function assignOrganizationByDomain(
|
|
|
118
111
|
return;
|
|
119
112
|
}
|
|
120
113
|
|
|
114
|
+
// Support comma-separated domains for multi-domain SSO
|
|
115
|
+
// First try exact match (fast path)
|
|
121
116
|
const whereClause: { field: string; value: string | boolean }[] = [
|
|
122
117
|
{ field: "domain", value: domain },
|
|
123
118
|
];
|
|
@@ -126,13 +121,25 @@ export async function assignOrganizationByDomain(
|
|
|
126
121
|
whereClause.push({ field: "domainVerified", value: true });
|
|
127
122
|
}
|
|
128
123
|
|
|
129
|
-
|
|
130
|
-
SSOProvider<SSOOptions>
|
|
131
|
-
>({
|
|
124
|
+
let ssoProvider = await ctx.context.adapter.findOne<SSOProvider<SSOOptions>>({
|
|
132
125
|
model: "ssoProvider",
|
|
133
126
|
where: whereClause,
|
|
134
127
|
});
|
|
135
128
|
|
|
129
|
+
// If not found, search all providers for comma-separated domain match
|
|
130
|
+
if (!ssoProvider) {
|
|
131
|
+
const allProviders = await ctx.context.adapter.findMany<
|
|
132
|
+
SSOProvider<SSOOptions>
|
|
133
|
+
>({
|
|
134
|
+
model: "ssoProvider",
|
|
135
|
+
where: domainVerification?.enabled
|
|
136
|
+
? [{ field: "domainVerified", value: true }]
|
|
137
|
+
: [],
|
|
138
|
+
});
|
|
139
|
+
ssoProvider =
|
|
140
|
+
allProviders.find((p) => domainMatches(domain, p.domain)) ?? null;
|
|
141
|
+
}
|
|
142
|
+
|
|
136
143
|
if (!ssoProvider || !ssoProvider.organizationId) {
|
|
137
144
|
return;
|
|
138
145
|
}
|
package/src/oidc.test.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
|
7
7
|
import { sso } from ".";
|
|
8
8
|
import { ssoClient } from "./client";
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
const server = new OAuth2Server();
|
|
11
11
|
|
|
12
12
|
describe("SSO", async () => {
|
|
13
13
|
const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
|
|
@@ -253,6 +253,118 @@ describe("SSO", async () => {
|
|
|
253
253
|
const { callbackURL } = await simulateOAuthFlow(res.url, headers);
|
|
254
254
|
expect(callbackURL).toContain("/dashboard");
|
|
255
255
|
});
|
|
256
|
+
|
|
257
|
+
it("should normalize email to lowercase in OIDC authentication", async () => {
|
|
258
|
+
const { headers } = await signInWithTestUser();
|
|
259
|
+
|
|
260
|
+
// Register a new provider for this test
|
|
261
|
+
await auth.api.registerSSOProvider({
|
|
262
|
+
body: {
|
|
263
|
+
providerId: "email-case-oidc-provider",
|
|
264
|
+
issuer: server.issuer.url!,
|
|
265
|
+
domain: "email-case-test.com",
|
|
266
|
+
oidcConfig: {
|
|
267
|
+
clientId: "email-case-test-client",
|
|
268
|
+
clientSecret: "test-client-secret",
|
|
269
|
+
discoveryEndpoint: `${server.issuer.url!}/.well-known/openid-configuration`,
|
|
270
|
+
pkce: false,
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
headers,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Store original listeners and set up mixed-case email
|
|
277
|
+
const originalUserinfoListeners =
|
|
278
|
+
server.service.listeners("beforeUserinfo");
|
|
279
|
+
const originalTokenListeners =
|
|
280
|
+
server.service.listeners("beforeTokenSigning");
|
|
281
|
+
|
|
282
|
+
server.service.removeAllListeners("beforeUserinfo");
|
|
283
|
+
server.service.removeAllListeners("beforeTokenSigning");
|
|
284
|
+
|
|
285
|
+
const mixedCaseEmail = "OIDCUser@Example.COM";
|
|
286
|
+
|
|
287
|
+
server.service.on("beforeUserinfo", (userInfoResponse) => {
|
|
288
|
+
userInfoResponse.body = {
|
|
289
|
+
email: mixedCaseEmail,
|
|
290
|
+
name: "OIDC Test User",
|
|
291
|
+
sub: "oidc-email-case-test-user",
|
|
292
|
+
picture: "https://test.com/picture.png",
|
|
293
|
+
email_verified: true,
|
|
294
|
+
};
|
|
295
|
+
userInfoResponse.statusCode = 200;
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
server.service.on("beforeTokenSigning", (token) => {
|
|
299
|
+
token.payload.email = mixedCaseEmail;
|
|
300
|
+
token.payload.email_verified = true;
|
|
301
|
+
token.payload.name = "OIDC Test User";
|
|
302
|
+
token.payload.sub = "oidc-email-case-test-user";
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
// First sign in - should create user with lowercase email
|
|
307
|
+
const signInHeaders1 = new Headers();
|
|
308
|
+
const res1 = await authClient.signIn.sso({
|
|
309
|
+
email: `user@email-case-test.com`,
|
|
310
|
+
callbackURL: "/dashboard",
|
|
311
|
+
fetchOptions: {
|
|
312
|
+
throw: true,
|
|
313
|
+
onSuccess: cookieSetter(signInHeaders1),
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const { callbackURL: callbackURL1, headers: sessionHeaders1 } =
|
|
318
|
+
await simulateOAuthFlow(res1.url, signInHeaders1);
|
|
319
|
+
expect(callbackURL1).toContain("/dashboard");
|
|
320
|
+
|
|
321
|
+
// Get session and verify email is lowercase
|
|
322
|
+
const session1 = await authClient.getSession({
|
|
323
|
+
fetchOptions: {
|
|
324
|
+
headers: sessionHeaders1,
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
expect(session1.data?.user.email).toBe("oidcuser@example.com");
|
|
329
|
+
const firstUserId = session1.data?.user.id;
|
|
330
|
+
expect(firstUserId).toBeDefined();
|
|
331
|
+
|
|
332
|
+
// Second sign in with same mixed-case email - should find existing user
|
|
333
|
+
const signInHeaders2 = new Headers();
|
|
334
|
+
const res2 = await authClient.signIn.sso({
|
|
335
|
+
email: `user@email-case-test.com`,
|
|
336
|
+
callbackURL: "/dashboard",
|
|
337
|
+
fetchOptions: {
|
|
338
|
+
throw: true,
|
|
339
|
+
onSuccess: cookieSetter(signInHeaders2),
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const { callbackURL: callbackURL2, headers: sessionHeaders2 } =
|
|
344
|
+
await simulateOAuthFlow(res2.url, signInHeaders2);
|
|
345
|
+
expect(callbackURL2).toContain("/dashboard");
|
|
346
|
+
|
|
347
|
+
// Verify same user is returned
|
|
348
|
+
const session2 = await authClient.getSession({
|
|
349
|
+
fetchOptions: {
|
|
350
|
+
headers: sessionHeaders2,
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
expect(session2.data?.user.id).toBe(firstUserId);
|
|
355
|
+
expect(session2.data?.user.email).toBe("oidcuser@example.com");
|
|
356
|
+
} finally {
|
|
357
|
+
// Restore original listeners
|
|
358
|
+
server.service.removeAllListeners("beforeUserinfo");
|
|
359
|
+
server.service.removeAllListeners("beforeTokenSigning");
|
|
360
|
+
for (const listener of originalUserinfoListeners) {
|
|
361
|
+
server.service.on("beforeUserinfo", listener);
|
|
362
|
+
}
|
|
363
|
+
for (const listener of originalTokenListeners) {
|
|
364
|
+
server.service.on("beforeTokenSigning", listener);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
});
|
|
256
368
|
});
|
|
257
369
|
|
|
258
370
|
describe("SSO disable implicit sign in", async () => {
|