@robelest/convex-auth 0.0.4-preview.27 → 0.0.4-preview.28
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/README.md +3 -5
- package/dist/bin.js +6488 -1571
- package/dist/browser/index.js +10 -7
- package/dist/browser/locks.js +3 -5
- package/dist/browser/navigation.js +7 -10
- package/dist/browser/runtime.js +35 -33
- package/dist/client/core/types.js +17 -0
- package/dist/client/factors/device.js +26 -19
- package/dist/client/index.js +151 -163
- package/dist/client/runtime/proxy.js +6 -6
- package/dist/client/services/adapters.js +3 -7
- package/dist/client/services/http.js +2 -5
- package/dist/client/services/resolve.js +5 -11
- package/dist/client/services/runtime.js +2 -5
- package/dist/component/_generated/component.d.ts +46 -0
- package/dist/component/index.d.ts +3 -3
- package/dist/component/model.d.ts +25 -25
- package/dist/component/public/identity/sessions.js +38 -1
- package/dist/component/public/identity/tokens.js +81 -3
- package/dist/component/public/identity/verifiers.js +9 -3
- package/dist/component/public.js +3 -3
- package/dist/component/schema.d.ts +320 -320
- package/dist/core/index.d.ts +380 -0
- package/dist/core/index.js +83 -0
- package/dist/otel.d.ts +13 -17
- package/dist/otel.js +39 -49
- package/dist/providers/email.d.ts +2 -2
- package/dist/providers/password.js +8 -16
- package/dist/providers/phone.js +2 -9
- package/dist/server/auth-context.d.ts +204 -0
- package/dist/server/auth-context.js +76 -0
- package/dist/server/auth.d.ts +25 -187
- package/dist/server/auth.js +5 -96
- package/dist/server/componentContext.d.ts +12 -0
- package/dist/server/componentContext.js +1 -0
- package/dist/server/config.js +1 -12
- package/dist/server/constants.js +6 -0
- package/dist/server/contract.d.ts +1 -1
- package/dist/server/core.js +5 -14
- package/dist/server/crypto.js +26 -18
- package/dist/server/db.js +6 -1
- package/dist/server/device.js +88 -78
- package/dist/server/http.d.ts +4 -3
- package/dist/server/http.js +74 -86
- package/dist/server/index.d.ts +2 -1
- package/dist/server/limits.js +22 -15
- package/dist/server/mounts.d.ts +103 -103
- package/dist/server/mutations/account.js +6 -4
- package/dist/server/mutations/invalidate.js +3 -6
- package/dist/server/mutations/oauth.js +86 -88
- package/dist/server/mutations/refresh.js +45 -87
- package/dist/server/mutations/register.js +19 -19
- package/dist/server/mutations/retrieve.js +17 -15
- package/dist/server/mutations/signature.js +9 -13
- package/dist/server/mutations/signin.js +7 -3
- package/dist/server/mutations/signout.js +10 -15
- package/dist/server/mutations/store.js +22 -12
- package/dist/server/mutations/verifier.js +11 -6
- package/dist/server/mutations/verify.js +55 -46
- package/dist/server/oauth/runtime.js +27 -25
- package/dist/server/passkey.js +299 -250
- package/dist/server/prefetch.js +283 -281
- package/dist/server/refresh.js +7 -60
- package/dist/server/runtime.d.ts +82 -206
- package/dist/server/runtime.js +63 -56
- package/dist/server/services/config.js +5 -3
- package/dist/server/services/logger.js +2 -4
- package/dist/server/services/providers.js +2 -4
- package/dist/server/services/refresh.js +2 -4
- package/dist/server/services/resolve.js +15 -14
- package/dist/server/services/signin.js +2 -4
- package/dist/server/sessions.js +32 -33
- package/dist/server/signin.js +177 -142
- package/dist/server/sso/domain.d.ts +20 -68
- package/dist/server/sso/domain.js +444 -413
- package/dist/server/sso/http.js +53 -59
- package/dist/server/sso/oidc.js +94 -80
- package/dist/server/tokens.js +13 -3
- package/dist/server/totp.js +153 -116
- package/dist/server/types.d.ts +2 -2
- package/dist/server/users.js +18 -23
- package/dist/server/utils/cache.js +51 -0
- package/dist/server/utils/dispatch.js +36 -0
- package/dist/server/utils/retry.js +24 -0
- package/dist/server/utils/span.js +32 -0
- package/dist/shared/errors.js +9 -3
- package/dist/shared/log.js +20 -22
- package/package.json +41 -33
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { log } from "../log.js";
|
|
2
2
|
import { addConnectionDomain, createGroupConnection, deleteConnectionDomain, deleteConnectionDomainVerification, deleteGroupConnection, getConnectionDomainVerification, getGroupConnection, getGroupConnectionByDomain, getScimConfigByConnection, listAuditEvents, listConnectionDomains, listGroupConnections, updateGroupConnection, upsertConnectionDomainVerification, upsertGroupConnectionSecret, verifyConnectionDomain } from "../contract.js";
|
|
3
|
+
import { retryWithBackoff } from "../utils/retry.js";
|
|
3
4
|
import { getGroupOidcUrls, getGroupSamlUrls, groupOidcProviderId, groupSamlProviderId, normalizeDomain } from "./shared.js";
|
|
4
5
|
import { getOidcConfig, getPublicOidcConfig, getSamlConfig, upsertProtocolConfig, withOidcSecretState } from "./config.js";
|
|
5
6
|
import { createGroupPolicyDomain } from "./policies.js";
|
|
@@ -7,17 +8,9 @@ import { createGroupScimDomain } from "./provision.js";
|
|
|
7
8
|
import { createServiceProviderMetadata, getSamlServiceProviderOptions, parseSamlIdpMetadataChecked } from "./saml.js";
|
|
8
9
|
import { createGroupWebhookDomain } from "./webhook.js";
|
|
9
10
|
import { ConvexError } from "convex/values";
|
|
10
|
-
import { Effect, Schedule } from "effect";
|
|
11
11
|
|
|
12
12
|
//#region src/server/sso/domain.ts
|
|
13
|
-
const NETWORK_RETRY_SCHEDULE = Schedule.both(Schedule.jittered(Schedule.exponential("200 millis")), Schedule.recurs(2));
|
|
14
13
|
const convexError = (data) => new ConvexError(data);
|
|
15
|
-
const toSsoError = (error) => error instanceof ConvexError ? error : typeof error === "object" && error !== null && "code" in error && "message" in error ? convexError(error) : error;
|
|
16
|
-
const tryPromise = (options) => Effect.tryPromise({
|
|
17
|
-
try: async () => options.try(),
|
|
18
|
-
catch: (error) => toSsoError(options.catch(error))
|
|
19
|
-
});
|
|
20
|
-
const runSsoBoundary = (effect) => Effect.runPromise(effect);
|
|
21
14
|
/**
|
|
22
15
|
* Build the connection and SSO management domain.
|
|
23
16
|
*/
|
|
@@ -412,239 +405,258 @@ function createGroupConnectionDomain(deps) {
|
|
|
412
405
|
}
|
|
413
406
|
},
|
|
414
407
|
saml: {
|
|
415
|
-
configure: (ctx, data) => {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
})
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
408
|
+
configure: async (ctx, data) => {
|
|
409
|
+
let connection;
|
|
410
|
+
try {
|
|
411
|
+
connection = await getGroupConnection(ctx, config.component.public, data.connectionId);
|
|
412
|
+
} catch {
|
|
413
|
+
throw convexError({
|
|
414
|
+
code: "INTERNAL_ERROR",
|
|
415
|
+
message: "Failed to load connection."
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
if (connection === null) throw convexError({
|
|
419
|
+
code: "INVALID_PARAMETERS",
|
|
420
|
+
message: connectionNotFoundError
|
|
421
|
+
});
|
|
422
|
+
if (connection.protocol !== "saml") throw convexError({
|
|
423
|
+
code: "INVALID_PARAMETERS",
|
|
424
|
+
message: "This connection is not a SAML connection."
|
|
425
|
+
});
|
|
426
|
+
const metadataUrl = typeof data.metadata.url === "string" && data.metadata.url.length > 0 ? data.metadata.url : void 0;
|
|
427
|
+
let metadataXml;
|
|
428
|
+
if (metadataUrl) try {
|
|
429
|
+
metadataXml = await retryWithBackoff(async () => {
|
|
430
|
+
const response = await fetch(metadataUrl, { signal: AbortSignal.timeout(1e4) });
|
|
431
|
+
if (!response.ok) throw new Error(`Failed to fetch SAML metadata: ${response.status}`);
|
|
432
|
+
return await response.text();
|
|
433
|
+
});
|
|
434
|
+
} catch (error) {
|
|
435
|
+
throw convexError({
|
|
428
436
|
code: "INVALID_PARAMETERS",
|
|
429
|
-
message:
|
|
430
|
-
})
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
442
|
-
})
|
|
437
|
+
message: error instanceof Error ? error.message : "Failed to fetch SAML metadata"
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
else if (data.metadata.xml) metadataXml = data.metadata.xml;
|
|
441
|
+
else throw convexError({
|
|
442
|
+
code: "INVALID_PARAMETERS",
|
|
443
|
+
message: "SAML registration requires metadataXml or metadataUrl."
|
|
444
|
+
});
|
|
445
|
+
let parsed;
|
|
446
|
+
try {
|
|
447
|
+
parsed = parseSamlIdpMetadataChecked({
|
|
448
|
+
metadataXml,
|
|
449
|
+
config: { protocols: { saml: { security: data.security } } }
|
|
450
|
+
});
|
|
451
|
+
} catch (error) {
|
|
452
|
+
throw convexError({
|
|
443
453
|
code: "INVALID_PARAMETERS",
|
|
444
|
-
message:
|
|
445
|
-
}));
|
|
446
|
-
const parsed = yield* tryPromise({
|
|
447
|
-
try: () => parseSamlIdpMetadataChecked({
|
|
448
|
-
metadataXml,
|
|
449
|
-
config: { protocols: { saml: { security: data.security } } }
|
|
450
|
-
}),
|
|
451
|
-
catch: (error) => ({
|
|
452
|
-
code: "INVALID_PARAMETERS",
|
|
453
|
-
message: error instanceof Error ? `Failed to parse SAML metadata: ${error.message}` : "Failed to parse SAML metadata."
|
|
454
|
-
})
|
|
454
|
+
message: error instanceof Error ? `Failed to parse SAML metadata: ${error.message}` : "Failed to parse SAML metadata."
|
|
455
455
|
});
|
|
456
|
-
|
|
457
|
-
|
|
456
|
+
}
|
|
457
|
+
log("DEBUG", "[group-sso] saml:configure:parsed", {
|
|
458
|
+
connectionId: data.connectionId,
|
|
459
|
+
metadataUrl,
|
|
460
|
+
entityId: parsed.entityId,
|
|
461
|
+
issuer: parsed.issuer
|
|
462
|
+
});
|
|
463
|
+
const baseConfig = upsertProtocolConfig(connection.config, "saml", {
|
|
464
|
+
enabled: true,
|
|
465
|
+
idp: {
|
|
458
466
|
metadataUrl,
|
|
459
|
-
|
|
460
|
-
|
|
467
|
+
metadataXml,
|
|
468
|
+
...parsed
|
|
469
|
+
},
|
|
470
|
+
serviceProvider: data.serviceProvider,
|
|
471
|
+
request: {
|
|
472
|
+
signAuthnRequests: data.request?.signAuthnRequests ?? parsed.wantsSignedAuthnRequests,
|
|
473
|
+
nameIdFormat: data.request?.nameIdFormat,
|
|
474
|
+
forceAuthn: data.request?.forceAuthn,
|
|
475
|
+
authnContextClassRefs: data.request?.authnContextClassRefs
|
|
476
|
+
},
|
|
477
|
+
profile: {
|
|
478
|
+
mapping: data.profile?.mapping,
|
|
479
|
+
extraFields: data.profile?.extraFields
|
|
480
|
+
},
|
|
481
|
+
security: data.security
|
|
482
|
+
});
|
|
483
|
+
const normalizedDomains = data.domains?.map(normalizeDomain);
|
|
484
|
+
const nextConfig = normalizedDomains ? {
|
|
485
|
+
...baseConfig,
|
|
486
|
+
domains: normalizedDomains
|
|
487
|
+
} : baseConfig;
|
|
488
|
+
const nextSamlConfig = nextConfig.protocols?.saml ?? void 0;
|
|
489
|
+
log("DEBUG", "[group-sso] saml:configure:nextConfig", {
|
|
490
|
+
connectionId: data.connectionId,
|
|
491
|
+
entityId: nextSamlConfig?.idp?.entityId ?? null,
|
|
492
|
+
issuer: nextSamlConfig?.idp?.issuer ?? null,
|
|
493
|
+
metadataUrl: nextSamlConfig?.idp?.metadataUrl ?? null,
|
|
494
|
+
hasMetadataXml: typeof nextSamlConfig?.idp?.metadataXml === "string"
|
|
495
|
+
});
|
|
496
|
+
try {
|
|
497
|
+
await updateGroupConnection(ctx, config.component.public, {
|
|
498
|
+
connectionId: connection._id,
|
|
499
|
+
data: {
|
|
500
|
+
status: "active",
|
|
501
|
+
config: nextConfig
|
|
502
|
+
}
|
|
461
503
|
});
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
metadataXml,
|
|
467
|
-
...parsed
|
|
468
|
-
},
|
|
469
|
-
serviceProvider: data.serviceProvider,
|
|
470
|
-
request: {
|
|
471
|
-
signAuthnRequests: data.request?.signAuthnRequests ?? parsed.wantsSignedAuthnRequests,
|
|
472
|
-
nameIdFormat: data.request?.nameIdFormat,
|
|
473
|
-
forceAuthn: data.request?.forceAuthn,
|
|
474
|
-
authnContextClassRefs: data.request?.authnContextClassRefs
|
|
475
|
-
},
|
|
476
|
-
profile: {
|
|
477
|
-
mapping: data.profile?.mapping,
|
|
478
|
-
extraFields: data.profile?.extraFields
|
|
479
|
-
},
|
|
480
|
-
security: data.security
|
|
504
|
+
} catch {
|
|
505
|
+
throw convexError({
|
|
506
|
+
code: "INTERNAL_ERROR",
|
|
507
|
+
message: "Failed to persist SAML registration."
|
|
481
508
|
});
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
connectionId: data.connectionId,
|
|
490
|
-
entityId: nextSamlConfig?.idp?.entityId ?? null,
|
|
491
|
-
issuer: nextSamlConfig?.idp?.issuer ?? null,
|
|
492
|
-
metadataUrl: nextSamlConfig?.idp?.metadataUrl ?? null,
|
|
493
|
-
hasMetadataXml: typeof nextSamlConfig?.idp?.metadataXml === "string"
|
|
509
|
+
}
|
|
510
|
+
if (normalizedDomains) for (const [index, domain] of normalizedDomains.entries()) try {
|
|
511
|
+
await addConnectionDomain(ctx, config.component.public, {
|
|
512
|
+
connectionId: connection._id,
|
|
513
|
+
groupId: connection.groupId,
|
|
514
|
+
domain,
|
|
515
|
+
isPrimary: index === 0
|
|
494
516
|
});
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
status: "active",
|
|
500
|
-
config: nextConfig
|
|
501
|
-
}
|
|
502
|
-
}),
|
|
503
|
-
catch: () => ({
|
|
504
|
-
code: "INTERNAL_ERROR",
|
|
505
|
-
message: "Failed to persist SAML registration."
|
|
506
|
-
})
|
|
517
|
+
} catch {
|
|
518
|
+
throw convexError({
|
|
519
|
+
code: "INTERNAL_ERROR",
|
|
520
|
+
message: "Failed to persist connection domain."
|
|
507
521
|
});
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
522
|
+
}
|
|
523
|
+
try {
|
|
524
|
+
await recordGroupAuditEvent(ctx, {
|
|
525
|
+
connectionId: connection._id,
|
|
526
|
+
groupId: connection.groupId,
|
|
527
|
+
eventType: "group.sso.saml.registered",
|
|
528
|
+
actorType: "system",
|
|
529
|
+
subjectType: "group_connection_saml",
|
|
530
|
+
subjectId: connection._id,
|
|
531
|
+
ok: true,
|
|
532
|
+
metadata: {
|
|
533
|
+
metadataUrl,
|
|
534
|
+
domains: normalizedDomains
|
|
535
|
+
}
|
|
519
536
|
});
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
eventType: "group.sso.saml.registered",
|
|
525
|
-
actorType: "system",
|
|
526
|
-
subjectType: "group_connection_saml",
|
|
527
|
-
subjectId: connection._id,
|
|
528
|
-
ok: true,
|
|
529
|
-
metadata: {
|
|
530
|
-
metadataUrl,
|
|
531
|
-
domains: normalizedDomains
|
|
532
|
-
}
|
|
533
|
-
}),
|
|
534
|
-
catch: () => ({
|
|
535
|
-
code: "INTERNAL_ERROR",
|
|
536
|
-
message: "Failed to record SAML registration audit event."
|
|
537
|
-
})
|
|
537
|
+
} catch {
|
|
538
|
+
throw convexError({
|
|
539
|
+
code: "INTERNAL_ERROR",
|
|
540
|
+
message: "Failed to record SAML registration audit event."
|
|
538
541
|
});
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
}
|
|
542
|
+
}
|
|
543
|
+
return {
|
|
544
|
+
connectionId: connection._id,
|
|
545
|
+
groupId: connection.groupId
|
|
546
|
+
};
|
|
544
547
|
},
|
|
545
|
-
refresh: (ctx, data) => {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
})
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
548
|
+
refresh: async (ctx, data) => {
|
|
549
|
+
let connection;
|
|
550
|
+
try {
|
|
551
|
+
connection = await getGroupConnection(ctx, config.component.public, data.connectionId);
|
|
552
|
+
} catch {
|
|
553
|
+
throw convexError({
|
|
554
|
+
code: "INTERNAL_ERROR",
|
|
555
|
+
message: "Failed to load connection."
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
if (connection === null) throw convexError({
|
|
559
|
+
code: "INVALID_PARAMETERS",
|
|
560
|
+
message: connectionNotFoundError
|
|
561
|
+
});
|
|
562
|
+
const samlConfig = connection.config?.protocols?.saml;
|
|
563
|
+
if (connection.protocol !== "saml") throw convexError({
|
|
564
|
+
code: "INVALID_PARAMETERS",
|
|
565
|
+
message: "This connection is not a SAML connection."
|
|
566
|
+
});
|
|
567
|
+
if (typeof samlConfig?.idp?.metadataUrl !== "string") throw convexError({
|
|
568
|
+
code: "INVALID_PARAMETERS",
|
|
569
|
+
message: "SAML metadataUrl is not configured."
|
|
570
|
+
});
|
|
571
|
+
const metadataUrl = samlConfig.idp.metadataUrl;
|
|
572
|
+
let metadataXml;
|
|
573
|
+
try {
|
|
574
|
+
metadataXml = await retryWithBackoff(async () => {
|
|
575
|
+
const response = await fetch(metadataUrl, { signal: AbortSignal.timeout(1e4) });
|
|
576
|
+
if (!response.ok) throw new Error(`Failed to fetch SAML metadata: ${response.status}`);
|
|
577
|
+
return await response.text();
|
|
578
|
+
});
|
|
579
|
+
} catch (error) {
|
|
580
|
+
throw convexError({
|
|
563
581
|
code: "INVALID_PARAMETERS",
|
|
564
|
-
message:
|
|
565
|
-
}));
|
|
566
|
-
const metadataUrl = samlConfig.idp.metadataUrl;
|
|
567
|
-
const response = yield* tryPromise({
|
|
568
|
-
try: async () => {
|
|
569
|
-
const response$1 = await fetch(metadataUrl, { signal: AbortSignal.timeout(1e4) });
|
|
570
|
-
if (!response$1.ok) throw new Error(`Failed to fetch SAML metadata: ${response$1.status}`);
|
|
571
|
-
return await response$1.text();
|
|
572
|
-
},
|
|
573
|
-
catch: (error) => ({
|
|
574
|
-
code: "INVALID_PARAMETERS",
|
|
575
|
-
message: error instanceof Error ? error.message : "Failed to fetch SAML metadata"
|
|
576
|
-
})
|
|
577
|
-
}).pipe(Effect.retry({ schedule: NETWORK_RETRY_SCHEDULE }));
|
|
578
|
-
const parsed = yield* tryPromise({
|
|
579
|
-
try: () => parseSamlIdpMetadataChecked({
|
|
580
|
-
metadataXml: response,
|
|
581
|
-
config: connection.config
|
|
582
|
-
}),
|
|
583
|
-
catch: (error) => ({
|
|
584
|
-
code: "INVALID_PARAMETERS",
|
|
585
|
-
message: error instanceof Error ? `Failed to parse SAML metadata: ${error.message}` : "Failed to parse SAML metadata."
|
|
586
|
-
})
|
|
582
|
+
message: error instanceof Error ? error.message : "Failed to fetch SAML metadata"
|
|
587
583
|
});
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
},
|
|
595
|
-
serviceProvider: samlConfig.serviceProvider,
|
|
596
|
-
request: samlConfig.request,
|
|
597
|
-
profile: samlConfig.profile,
|
|
598
|
-
security: samlConfig.security
|
|
584
|
+
}
|
|
585
|
+
let parsed;
|
|
586
|
+
try {
|
|
587
|
+
parsed = parseSamlIdpMetadataChecked({
|
|
588
|
+
metadataXml,
|
|
589
|
+
config: connection.config
|
|
599
590
|
});
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
status: connection.status,
|
|
605
|
-
config: nextConfig
|
|
606
|
-
}
|
|
607
|
-
}),
|
|
608
|
-
catch: () => ({
|
|
609
|
-
code: "INTERNAL_ERROR",
|
|
610
|
-
message: "Failed to persist refreshed SAML metadata."
|
|
611
|
-
})
|
|
591
|
+
} catch (error) {
|
|
592
|
+
throw convexError({
|
|
593
|
+
code: "INVALID_PARAMETERS",
|
|
594
|
+
message: error instanceof Error ? `Failed to parse SAML metadata: ${error.message}` : "Failed to parse SAML metadata."
|
|
612
595
|
});
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
596
|
+
}
|
|
597
|
+
const nextConfig = upsertProtocolConfig(connection.config, "saml", {
|
|
598
|
+
enabled: true,
|
|
599
|
+
idp: {
|
|
600
|
+
metadataUrl,
|
|
601
|
+
metadataXml,
|
|
602
|
+
...parsed
|
|
603
|
+
},
|
|
604
|
+
serviceProvider: samlConfig.serviceProvider,
|
|
605
|
+
request: samlConfig.request,
|
|
606
|
+
profile: samlConfig.profile,
|
|
607
|
+
security: samlConfig.security
|
|
608
|
+
});
|
|
609
|
+
try {
|
|
610
|
+
await updateGroupConnection(ctx, config.component.public, {
|
|
611
|
+
connectionId: connection._id,
|
|
612
|
+
data: {
|
|
613
|
+
status: connection.status,
|
|
614
|
+
config: nextConfig
|
|
615
|
+
}
|
|
628
616
|
});
|
|
629
|
-
|
|
617
|
+
} catch {
|
|
618
|
+
throw convexError({
|
|
619
|
+
code: "INTERNAL_ERROR",
|
|
620
|
+
message: "Failed to persist refreshed SAML metadata."
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
try {
|
|
624
|
+
await recordGroupAuditEvent(ctx, {
|
|
630
625
|
connectionId: connection._id,
|
|
631
|
-
groupId: connection.groupId
|
|
632
|
-
|
|
633
|
-
|
|
626
|
+
groupId: connection.groupId,
|
|
627
|
+
eventType: "group.sso.saml.refreshed",
|
|
628
|
+
actorType: "system",
|
|
629
|
+
subjectType: "group_connection_saml",
|
|
630
|
+
subjectId: connection._id,
|
|
631
|
+
ok: true,
|
|
632
|
+
metadata: { metadataUrl }
|
|
633
|
+
});
|
|
634
|
+
} catch {
|
|
635
|
+
throw convexError({
|
|
636
|
+
code: "INTERNAL_ERROR",
|
|
637
|
+
message: "Failed to record SAML refresh audit event."
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
return {
|
|
641
|
+
connectionId: connection._id,
|
|
642
|
+
groupId: connection.groupId
|
|
643
|
+
};
|
|
634
644
|
},
|
|
635
|
-
get: (ctx, connectionId) => {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
})
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
645
|
+
get: async (ctx, connectionId) => {
|
|
646
|
+
let connection;
|
|
647
|
+
try {
|
|
648
|
+
connection = await getGroupConnection(ctx, config.component.public, connectionId);
|
|
649
|
+
} catch {
|
|
650
|
+
throw convexError({
|
|
651
|
+
code: "INTERNAL_ERROR",
|
|
652
|
+
message: "Failed to load connection."
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
if (connection === null) throw convexError({
|
|
656
|
+
code: "INVALID_PARAMETERS",
|
|
657
|
+
message: connectionNotFoundError
|
|
658
|
+
});
|
|
659
|
+
return getSamlConfig(connection.config);
|
|
648
660
|
},
|
|
649
661
|
status: (ctx, connectionId) => {
|
|
650
662
|
return getGroupConnection(ctx, config.component.public, connectionId).then((connection) => {
|
|
@@ -786,141 +798,152 @@ function createGroupConnectionDomain(deps) {
|
|
|
786
798
|
},
|
|
787
799
|
policy,
|
|
788
800
|
oidc: {
|
|
789
|
-
configure: (ctx, data) => {
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
}).pipe(Effect.flatMap((connection$1) => connection$1 === null ? Effect.fail(convexError({
|
|
802
|
-
code: "INVALID_PARAMETERS",
|
|
803
|
-
message: connectionNotFoundError
|
|
804
|
-
})) : Effect.succeed(connection$1)));
|
|
805
|
-
if (connection.protocol !== "oidc") return yield* Effect.fail(convexError({
|
|
806
|
-
code: "INVALID_PARAMETERS",
|
|
807
|
-
message: "This connection is not an OIDC connection."
|
|
808
|
-
}));
|
|
809
|
-
const nextConfig = upsertProtocolConfig(connection.config, "oidc", {
|
|
810
|
-
enabled: true,
|
|
811
|
-
discovery: {
|
|
812
|
-
issuer: data.discovery.issuer,
|
|
813
|
-
discoveryUrl: data.discovery.discoveryUrl,
|
|
814
|
-
jwksUri: data.discovery.jwksUri,
|
|
815
|
-
audience: data.discovery.audience
|
|
816
|
-
},
|
|
817
|
-
client: {
|
|
818
|
-
id: data.client.id,
|
|
819
|
-
authMethod: data.client.authMethod
|
|
820
|
-
},
|
|
821
|
-
request: {
|
|
822
|
-
scopes: data.request?.scopes ?? [
|
|
823
|
-
"openid",
|
|
824
|
-
"profile",
|
|
825
|
-
"email"
|
|
826
|
-
],
|
|
827
|
-
loginHint: data.request?.loginHint,
|
|
828
|
-
authorizationParams: data.request?.authorizationParams
|
|
829
|
-
},
|
|
830
|
-
security: {
|
|
831
|
-
clockToleranceSeconds: data.security?.clockToleranceSeconds,
|
|
832
|
-
strictIssuer: data.security?.strictIssuer
|
|
833
|
-
},
|
|
834
|
-
profile: {
|
|
835
|
-
mapping: data.profile?.mapping,
|
|
836
|
-
extraFields: data.profile?.extraFields
|
|
837
|
-
}
|
|
801
|
+
configure: async (ctx, data) => {
|
|
802
|
+
if (data.discovery.issuer === void 0 && data.discovery.discoveryUrl === void 0) throw convexError({
|
|
803
|
+
code: "INVALID_PARAMETERS",
|
|
804
|
+
message: "OIDC registration requires issuer or discoveryUrl."
|
|
805
|
+
});
|
|
806
|
+
let connection;
|
|
807
|
+
try {
|
|
808
|
+
connection = await getGroupConnection(ctx, config.component.public, data.connectionId);
|
|
809
|
+
} catch {
|
|
810
|
+
throw convexError({
|
|
811
|
+
code: "INTERNAL_ERROR",
|
|
812
|
+
message: "Failed to load connection."
|
|
838
813
|
});
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
814
|
+
}
|
|
815
|
+
if (connection === null) throw convexError({
|
|
816
|
+
code: "INVALID_PARAMETERS",
|
|
817
|
+
message: connectionNotFoundError
|
|
818
|
+
});
|
|
819
|
+
if (connection.protocol !== "oidc") throw convexError({
|
|
820
|
+
code: "INVALID_PARAMETERS",
|
|
821
|
+
message: "This connection is not an OIDC connection."
|
|
822
|
+
});
|
|
823
|
+
const nextConfig = upsertProtocolConfig(connection.config, "oidc", {
|
|
824
|
+
enabled: true,
|
|
825
|
+
discovery: {
|
|
826
|
+
issuer: data.discovery.issuer,
|
|
827
|
+
discoveryUrl: data.discovery.discoveryUrl,
|
|
828
|
+
jwksUri: data.discovery.jwksUri,
|
|
829
|
+
audience: data.discovery.audience
|
|
830
|
+
},
|
|
831
|
+
client: {
|
|
832
|
+
id: data.client.id,
|
|
833
|
+
authMethod: data.client.authMethod
|
|
834
|
+
},
|
|
835
|
+
request: {
|
|
836
|
+
scopes: data.request?.scopes ?? [
|
|
837
|
+
"openid",
|
|
838
|
+
"profile",
|
|
839
|
+
"email"
|
|
840
|
+
],
|
|
841
|
+
loginHint: data.request?.loginHint,
|
|
842
|
+
authorizationParams: data.request?.authorizationParams
|
|
843
|
+
},
|
|
844
|
+
security: {
|
|
845
|
+
clockToleranceSeconds: data.security?.clockToleranceSeconds,
|
|
846
|
+
strictIssuer: data.security?.strictIssuer
|
|
847
|
+
},
|
|
848
|
+
profile: {
|
|
849
|
+
mapping: data.profile?.mapping,
|
|
850
|
+
extraFields: data.profile?.extraFields
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
try {
|
|
854
|
+
await updateGroupConnection(ctx, config.component.public, {
|
|
855
|
+
connectionId: data.connectionId,
|
|
856
|
+
data: { config: nextConfig }
|
|
848
857
|
});
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
updatedAt: Date.now()
|
|
864
|
-
}),
|
|
865
|
-
catch: () => ({
|
|
866
|
-
code: "INTERNAL_ERROR",
|
|
867
|
-
message: "Failed to persist OIDC client secret."
|
|
868
|
-
})
|
|
858
|
+
} catch {
|
|
859
|
+
throw convexError({
|
|
860
|
+
code: "INTERNAL_ERROR",
|
|
861
|
+
message: "Failed to persist OIDC registration."
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
if (data.client.secret !== void 0) {
|
|
865
|
+
let ciphertext;
|
|
866
|
+
try {
|
|
867
|
+
ciphertext = await encryptSecret(data.client.secret);
|
|
868
|
+
} catch {
|
|
869
|
+
throw convexError({
|
|
870
|
+
code: "INTERNAL_ERROR",
|
|
871
|
+
message: "Failed to encrypt OIDC client secret."
|
|
869
872
|
});
|
|
870
873
|
}
|
|
871
|
-
|
|
872
|
-
|
|
874
|
+
try {
|
|
875
|
+
await upsertGroupConnectionSecret(ctx, config.component.public, {
|
|
873
876
|
connectionId: data.connectionId,
|
|
874
877
|
groupId: connection.groupId,
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
issuer: data.discovery.issuer,
|
|
882
|
-
discoveryUrl: data.discovery.discoveryUrl,
|
|
883
|
-
jwksUri: data.discovery.jwksUri,
|
|
884
|
-
audience: data.discovery.audience,
|
|
885
|
-
tokenEndpointAuthMethod: data.client.authMethod
|
|
886
|
-
}
|
|
887
|
-
}),
|
|
888
|
-
catch: () => ({
|
|
878
|
+
kind: GROUP_CONNECTION_OIDC_CLIENT_SECRET_KIND,
|
|
879
|
+
ciphertext,
|
|
880
|
+
updatedAt: Date.now()
|
|
881
|
+
});
|
|
882
|
+
} catch {
|
|
883
|
+
throw convexError({
|
|
889
884
|
code: "INTERNAL_ERROR",
|
|
890
|
-
message: "Failed to
|
|
891
|
-
})
|
|
885
|
+
message: "Failed to persist OIDC client secret."
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
try {
|
|
890
|
+
await recordGroupAuditEvent(ctx, {
|
|
891
|
+
connectionId: data.connectionId,
|
|
892
|
+
groupId: connection.groupId,
|
|
893
|
+
eventType: "group.sso.oidc.registered",
|
|
894
|
+
actorType: "system",
|
|
895
|
+
subjectType: "group_connection_oidc",
|
|
896
|
+
subjectId: data.connectionId,
|
|
897
|
+
ok: true,
|
|
898
|
+
metadata: {
|
|
899
|
+
issuer: data.discovery.issuer,
|
|
900
|
+
discoveryUrl: data.discovery.discoveryUrl,
|
|
901
|
+
jwksUri: data.discovery.jwksUri,
|
|
902
|
+
audience: data.discovery.audience,
|
|
903
|
+
tokenEndpointAuthMethod: data.client.authMethod
|
|
904
|
+
}
|
|
892
905
|
});
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
message: "Failed to load OIDC secret metadata."
|
|
898
|
-
})
|
|
906
|
+
} catch {
|
|
907
|
+
throw convexError({
|
|
908
|
+
code: "INTERNAL_ERROR",
|
|
909
|
+
message: "Failed to record OIDC registration audit event."
|
|
899
910
|
});
|
|
900
|
-
|
|
901
|
-
|
|
911
|
+
}
|
|
912
|
+
let secret;
|
|
913
|
+
try {
|
|
914
|
+
secret = await getGroupConnectionSecret(ctx, data.connectionId, GROUP_CONNECTION_OIDC_CLIENT_SECRET_KIND);
|
|
915
|
+
} catch {
|
|
916
|
+
throw convexError({
|
|
917
|
+
code: "INTERNAL_ERROR",
|
|
918
|
+
message: "Failed to load OIDC secret metadata."
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
return withOidcSecretState(getPublicOidcConfig(nextConfig), secret !== null);
|
|
902
922
|
},
|
|
903
|
-
get: (ctx, connectionId) => {
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
}).pipe(Effect.flatMap((connection$1) => connection$1 === null ? Effect.fail(convexError({
|
|
912
|
-
code: "INVALID_PARAMETERS",
|
|
913
|
-
message: connectionNotFoundError
|
|
914
|
-
})) : Effect.succeed(connection$1)));
|
|
915
|
-
const secret = yield* tryPromise({
|
|
916
|
-
try: () => getGroupConnectionSecret(ctx, connection._id, GROUP_CONNECTION_OIDC_CLIENT_SECRET_KIND),
|
|
917
|
-
catch: () => ({
|
|
918
|
-
code: "INTERNAL_ERROR",
|
|
919
|
-
message: "Failed to load OIDC secret metadata."
|
|
920
|
-
})
|
|
923
|
+
get: async (ctx, connectionId) => {
|
|
924
|
+
let connection;
|
|
925
|
+
try {
|
|
926
|
+
connection = await getGroupConnection(ctx, config.component.public, connectionId);
|
|
927
|
+
} catch {
|
|
928
|
+
throw convexError({
|
|
929
|
+
code: "INTERNAL_ERROR",
|
|
930
|
+
message: "Failed to load connection."
|
|
921
931
|
});
|
|
922
|
-
|
|
923
|
-
|
|
932
|
+
}
|
|
933
|
+
if (connection === null) throw convexError({
|
|
934
|
+
code: "INVALID_PARAMETERS",
|
|
935
|
+
message: connectionNotFoundError
|
|
936
|
+
});
|
|
937
|
+
let secret;
|
|
938
|
+
try {
|
|
939
|
+
secret = await getGroupConnectionSecret(ctx, connection._id, GROUP_CONNECTION_OIDC_CLIENT_SECRET_KIND);
|
|
940
|
+
} catch {
|
|
941
|
+
throw convexError({
|
|
942
|
+
code: "INTERNAL_ERROR",
|
|
943
|
+
message: "Failed to load OIDC secret metadata."
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
return withOidcSecretState(getPublicOidcConfig(connection.config), secret !== null);
|
|
924
947
|
},
|
|
925
948
|
status: (ctx, connectionId) => {
|
|
926
949
|
return Promise.all([getGroupConnection(ctx, config.component.public, connectionId), getGroupConnectionSecret(ctx, connectionId, GROUP_CONNECTION_OIDC_CLIENT_SECRET_KIND)]).then(([connection, secret]) => {
|
|
@@ -948,91 +971,99 @@ function createGroupConnectionDomain(deps) {
|
|
|
948
971
|
};
|
|
949
972
|
});
|
|
950
973
|
},
|
|
951
|
-
signIn: (ctx, data) => {
|
|
974
|
+
signIn: async (ctx, data) => {
|
|
952
975
|
log("DEBUG", "[group-sso] resolver:start", {
|
|
953
976
|
connectionId: data.connectionId,
|
|
954
977
|
email: data.email,
|
|
955
978
|
domain: data.domain,
|
|
956
979
|
redirectTo: data.redirectTo
|
|
957
980
|
});
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
981
|
+
let connection;
|
|
982
|
+
if (data.connectionId !== void 0) {
|
|
983
|
+
try {
|
|
984
|
+
connection = await getGroupConnection(ctx, config.component.public, data.connectionId);
|
|
985
|
+
} catch {
|
|
986
|
+
throw convexError({
|
|
962
987
|
code: "INTERNAL_ERROR",
|
|
963
988
|
message: "Failed to load connection."
|
|
964
|
-
})
|
|
965
|
-
}
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
if (connection === null) throw convexError({
|
|
966
992
|
code: "INVALID_PARAMETERS",
|
|
967
993
|
message: connectionNotFoundError
|
|
968
|
-
})
|
|
969
|
-
|
|
970
|
-
|
|
994
|
+
});
|
|
995
|
+
} else if (data.domain !== void 0 || data.email !== void 0) {
|
|
996
|
+
let result;
|
|
997
|
+
try {
|
|
998
|
+
result = await getGroupConnectionByDomain(ctx, config.component.public, normalizeDomain(data.domain ?? String(data.email).split("@").pop() ?? ""));
|
|
999
|
+
} catch {
|
|
1000
|
+
throw convexError({
|
|
971
1001
|
code: "INTERNAL_ERROR",
|
|
972
1002
|
message: "Failed to resolve connection by domain."
|
|
973
|
-
})
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
message: "No group connection matched the provided input."
|
|
979
|
-
})))) : yield* Effect.fail(convexError({
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
log("DEBUG", "[group-sso] resolver:domainLookup", result);
|
|
1006
|
+
if (result?.connection && result.domain?.verifiedAt !== void 0) connection = result.connection;
|
|
1007
|
+
else throw convexError({
|
|
980
1008
|
code: "INVALID_PARAMETERS",
|
|
981
1009
|
message: "No group connection matched the provided input."
|
|
982
|
-
}));
|
|
983
|
-
if (connection.status !== "active") return yield* Effect.fail(convexError({
|
|
984
|
-
code: "INVALID_PARAMETERS",
|
|
985
|
-
message: "Group connection is not active."
|
|
986
|
-
}));
|
|
987
|
-
const protocol = resolveGroupConnectionProtocol(connection);
|
|
988
|
-
log("DEBUG", "[group-sso] resolver:connection", {
|
|
989
|
-
connectionId: connection._id,
|
|
990
|
-
status: connection.status,
|
|
991
|
-
protocol
|
|
992
1010
|
});
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
}
|
|
1011
|
-
});
|
|
1012
|
-
return {
|
|
1013
|
-
signInPath: `${requireEnv("CONVEX_SITE_URL")}/api/auth/connections/${connection._id}/saml/signin`,
|
|
1014
|
-
callbackPath: urls.acsUrl,
|
|
1015
|
-
providerId: groupSamlProviderId(connection._id)
|
|
1016
|
-
};
|
|
1017
|
-
})();
|
|
1018
|
-
log("DEBUG", "[group-sso] resolver:paths", {
|
|
1011
|
+
} else throw convexError({
|
|
1012
|
+
code: "INVALID_PARAMETERS",
|
|
1013
|
+
message: "No group connection matched the provided input."
|
|
1014
|
+
});
|
|
1015
|
+
if (connection.status !== "active") throw convexError({
|
|
1016
|
+
code: "INVALID_PARAMETERS",
|
|
1017
|
+
message: "Group connection is not active."
|
|
1018
|
+
});
|
|
1019
|
+
const protocol = resolveGroupConnectionProtocol(connection);
|
|
1020
|
+
log("DEBUG", "[group-sso] resolver:connection", {
|
|
1021
|
+
connectionId: connection._id,
|
|
1022
|
+
status: connection.status,
|
|
1023
|
+
protocol
|
|
1024
|
+
});
|
|
1025
|
+
const { signInPath, callbackPath, providerId } = protocol === "oidc" ? (() => {
|
|
1026
|
+
const urls = getGroupOidcUrls({
|
|
1027
|
+
rootUrl: requireEnv("CONVEX_SITE_URL"),
|
|
1019
1028
|
connectionId: connection._id,
|
|
1020
|
-
|
|
1021
|
-
callbackPath
|
|
1029
|
+
sharedRedirectURI: deps.sharedOidcRedirectURI
|
|
1022
1030
|
});
|
|
1023
1031
|
return {
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
providerId
|
|
1027
|
-
signInPath: protocol === "oidc" && typeof data.loginHint === "string" ? (() => {
|
|
1028
|
-
const signInUrl = new URL(signInPath);
|
|
1029
|
-
signInUrl.searchParams.set("loginHint", data.loginHint);
|
|
1030
|
-
return signInUrl.toString();
|
|
1031
|
-
})() : signInPath,
|
|
1032
|
-
callbackPath,
|
|
1033
|
-
redirectTo: data.redirectTo
|
|
1032
|
+
signInPath: urls.signInUrl,
|
|
1033
|
+
callbackPath: urls.callbackUrl,
|
|
1034
|
+
providerId: groupOidcProviderId(connection._id)
|
|
1034
1035
|
};
|
|
1035
|
-
}))
|
|
1036
|
+
})() : (() => {
|
|
1037
|
+
const urls = getGroupSamlUrls({
|
|
1038
|
+
rootUrl: requireEnv("CONVEX_SITE_URL"),
|
|
1039
|
+
source: {
|
|
1040
|
+
kind: "connection",
|
|
1041
|
+
id: connection._id
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
1044
|
+
return {
|
|
1045
|
+
signInPath: `${requireEnv("CONVEX_SITE_URL")}/api/auth/connections/${connection._id}/saml/signin`,
|
|
1046
|
+
callbackPath: urls.acsUrl,
|
|
1047
|
+
providerId: groupSamlProviderId(connection._id)
|
|
1048
|
+
};
|
|
1049
|
+
})();
|
|
1050
|
+
log("DEBUG", "[group-sso] resolver:paths", {
|
|
1051
|
+
connectionId: connection._id,
|
|
1052
|
+
signInPath,
|
|
1053
|
+
callbackPath
|
|
1054
|
+
});
|
|
1055
|
+
return {
|
|
1056
|
+
connectionId: connection._id,
|
|
1057
|
+
protocol,
|
|
1058
|
+
providerId,
|
|
1059
|
+
signInPath: protocol === "oidc" && typeof data.loginHint === "string" ? (() => {
|
|
1060
|
+
const signInUrl = new URL(signInPath);
|
|
1061
|
+
signInUrl.searchParams.set("loginHint", data.loginHint);
|
|
1062
|
+
return signInUrl.toString();
|
|
1063
|
+
})() : signInPath,
|
|
1064
|
+
callbackPath,
|
|
1065
|
+
redirectTo: data.redirectTo
|
|
1066
|
+
};
|
|
1036
1067
|
},
|
|
1037
1068
|
validate: async (ctx, connectionId) => {
|
|
1038
1069
|
const checks = [];
|