@effect-auth/core 0.1.0-alpha.1 → 0.1.0-alpha.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/README.md +728 -72
- package/dist/ApiKey.d.ts +207 -0
- package/dist/ApiKey.d.ts.map +1 -0
- package/dist/ApiKey.js +275 -0
- package/dist/ApiKey.js.map +1 -0
- package/dist/AuditLog.d.ts +275 -1
- package/dist/AuditLog.d.ts.map +1 -1
- package/dist/AuditLog.js +213 -1
- package/dist/AuditLog.js.map +1 -1
- package/dist/AuthConfig.d.ts +42 -0
- package/dist/AuthConfig.d.ts.map +1 -0
- package/dist/AuthConfig.js +43 -0
- package/dist/AuthConfig.js.map +1 -0
- package/dist/AuthFlow.d.ts +462 -36
- package/dist/AuthFlow.d.ts.map +1 -1
- package/dist/AuthFlow.js +657 -5
- package/dist/AuthFlow.js.map +1 -1
- package/dist/AuthKernel.d.ts +3 -0
- package/dist/AuthKernel.d.ts.map +1 -0
- package/dist/AuthKernel.js +9 -0
- package/dist/AuthKernel.js.map +1 -0
- package/dist/Challenge.d.ts +24 -4
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js +167 -1
- package/dist/Challenge.js.map +1 -1
- package/dist/Client.d.ts +378 -0
- package/dist/Client.d.ts.map +1 -0
- package/dist/Client.js +768 -0
- package/dist/Client.js.map +1 -0
- package/dist/Crypto.d.ts +6 -1
- package/dist/Crypto.d.ts.map +1 -1
- package/dist/Crypto.js +62 -1
- package/dist/Crypto.js.map +1 -1
- package/dist/EffectQbSqliteStorage.d.ts +600 -0
- package/dist/EffectQbSqliteStorage.d.ts.map +1 -0
- package/dist/EffectQbSqliteStorage.js +1637 -0
- package/dist/EffectQbSqliteStorage.js.map +1 -0
- package/dist/EmailOtp.d.ts +120 -0
- package/dist/EmailOtp.d.ts.map +1 -0
- package/dist/EmailOtp.js +286 -0
- package/dist/EmailOtp.js.map +1 -0
- package/dist/EmailVerification.d.ts +121 -0
- package/dist/EmailVerification.d.ts.map +1 -0
- package/dist/EmailVerification.js +200 -0
- package/dist/EmailVerification.js.map +1 -0
- package/dist/HttpApi/Api.d.ts +1956 -0
- package/dist/HttpApi/Api.d.ts.map +1 -0
- package/dist/HttpApi/Api.js +2325 -0
- package/dist/HttpApi/Api.js.map +1 -0
- package/dist/HttpApi/Endpoints.d.ts +945 -0
- package/dist/HttpApi/Endpoints.d.ts.map +1 -0
- package/dist/HttpApi/Endpoints.js +373 -0
- package/dist/HttpApi/Endpoints.js.map +1 -0
- package/dist/HttpApi/Errors.d.ts +82 -0
- package/dist/HttpApi/Errors.d.ts.map +1 -0
- package/dist/HttpApi/Errors.js +184 -0
- package/dist/HttpApi/Errors.js.map +1 -0
- package/dist/HttpApi/Schemas.d.ts +1113 -0
- package/dist/HttpApi/Schemas.d.ts.map +1 -0
- package/dist/HttpApi/Schemas.js +734 -0
- package/dist/HttpApi/Schemas.js.map +1 -0
- package/dist/HttpApi/Security.d.ts +159 -0
- package/dist/HttpApi/Security.d.ts.map +1 -0
- package/dist/HttpApi/Security.js +530 -0
- package/dist/HttpApi/Security.js.map +1 -0
- package/dist/HttpApi/Transport.d.ts +65 -0
- package/dist/HttpApi/Transport.d.ts.map +1 -0
- package/dist/HttpApi/Transport.js +311 -0
- package/dist/HttpApi/Transport.js.map +1 -0
- package/dist/HttpApi/index.d.ts +8 -0
- package/dist/HttpApi/index.d.ts.map +1 -0
- package/dist/HttpApi/index.js +7 -0
- package/dist/HttpApi/index.js.map +1 -0
- package/dist/HttpApi.d.ts +2 -0
- package/dist/HttpApi.d.ts.map +1 -0
- package/dist/HttpApi.js +2 -0
- package/dist/HttpApi.js.map +1 -0
- package/dist/Identifiers.d.ts.map +1 -1
- package/dist/Identifiers.js.map +1 -1
- package/dist/Internal.d.ts +7 -0
- package/dist/Internal.d.ts.map +1 -0
- package/dist/Internal.js +12 -0
- package/dist/Internal.js.map +1 -0
- package/dist/Jwt.d.ts +271 -0
- package/dist/Jwt.d.ts.map +1 -0
- package/dist/Jwt.js +512 -0
- package/dist/Jwt.js.map +1 -0
- package/dist/LoginApproval.d.ts +314 -0
- package/dist/LoginApproval.d.ts.map +1 -0
- package/dist/LoginApproval.js +551 -0
- package/dist/LoginApproval.js.map +1 -0
- package/dist/LoginNotification.d.ts +183 -0
- package/dist/LoginNotification.d.ts.map +1 -0
- package/dist/LoginNotification.js +329 -0
- package/dist/LoginNotification.js.map +1 -0
- package/dist/LoginRisk.d.ts +73 -0
- package/dist/LoginRisk.d.ts.map +1 -0
- package/dist/LoginRisk.js +57 -0
- package/dist/LoginRisk.js.map +1 -0
- package/dist/MagicLink.d.ts +85 -0
- package/dist/MagicLink.d.ts.map +1 -0
- package/dist/MagicLink.js +206 -0
- package/dist/MagicLink.js.map +1 -0
- package/dist/Mailer.d.ts +147 -0
- package/dist/Mailer.d.ts.map +1 -0
- package/dist/Mailer.js +208 -0
- package/dist/Mailer.js.map +1 -0
- package/dist/OAuth.d.ts +934 -0
- package/dist/OAuth.d.ts.map +1 -0
- package/dist/OAuth.js +1661 -0
- package/dist/OAuth.js.map +1 -0
- package/dist/Passkey.d.ts +334 -0
- package/dist/Passkey.d.ts.map +1 -0
- package/dist/Passkey.js +422 -0
- package/dist/Passkey.js.map +1 -0
- package/dist/PasskeySimpleWebAuthn.d.ts +11 -0
- package/dist/PasskeySimpleWebAuthn.d.ts.map +1 -0
- package/dist/PasskeySimpleWebAuthn.js +181 -0
- package/dist/PasskeySimpleWebAuthn.js.map +1 -0
- package/dist/Password.d.ts +237 -0
- package/dist/Password.d.ts.map +1 -0
- package/dist/Password.js +641 -0
- package/dist/Password.js.map +1 -0
- package/dist/Policy.d.ts +7 -4
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js +28 -5
- package/dist/Policy.js.map +1 -1
- package/dist/Privacy.d.ts +15 -0
- package/dist/Privacy.d.ts.map +1 -1
- package/dist/Privacy.js +58 -1
- package/dist/Privacy.js.map +1 -1
- package/dist/RateLimiter.d.ts +104 -0
- package/dist/RateLimiter.d.ts.map +1 -0
- package/dist/RateLimiter.js +118 -0
- package/dist/RateLimiter.js.map +1 -0
- package/dist/RecoveryCode.d.ts +169 -0
- package/dist/RecoveryCode.d.ts.map +1 -0
- package/dist/RecoveryCode.js +299 -0
- package/dist/RecoveryCode.js.map +1 -0
- package/dist/RefreshToken.d.ts +222 -0
- package/dist/RefreshToken.d.ts.map +1 -0
- package/dist/RefreshToken.js +342 -0
- package/dist/RefreshToken.js.map +1 -0
- package/dist/SecurityTimeline.d.ts +72 -0
- package/dist/SecurityTimeline.d.ts.map +1 -0
- package/dist/SecurityTimeline.js +386 -0
- package/dist/SecurityTimeline.js.map +1 -0
- package/dist/Sessions.d.ts +123 -11
- package/dist/Sessions.d.ts.map +1 -1
- package/dist/Sessions.js +534 -1
- package/dist/Sessions.js.map +1 -1
- package/dist/StepUp.d.ts +44 -0
- package/dist/StepUp.d.ts.map +1 -0
- package/dist/StepUp.js +82 -0
- package/dist/StepUp.js.map +1 -0
- package/dist/Storage.d.ts +73 -5
- package/dist/Storage.d.ts.map +1 -1
- package/dist/Storage.js +11 -1
- package/dist/Storage.js.map +1 -1
- package/dist/StorageMigrations.d.ts +13 -1
- package/dist/StorageMigrations.d.ts.map +1 -1
- package/dist/StorageMigrations.js +256 -2
- package/dist/StorageMigrations.js.map +1 -1
- package/dist/Testing.d.ts +9 -1
- package/dist/Testing.d.ts.map +1 -1
- package/dist/Testing.js +30 -2
- package/dist/Testing.js.map +1 -1
- package/dist/Totp.d.ts +220 -0
- package/dist/Totp.d.ts.map +1 -0
- package/dist/Totp.js +477 -0
- package/dist/Totp.js.map +1 -0
- package/dist/TrustedDevice.d.ts +127 -0
- package/dist/TrustedDevice.d.ts.map +1 -0
- package/dist/TrustedDevice.js +252 -0
- package/dist/TrustedDevice.js.map +1 -0
- package/dist/WaitUntil.d.ts.map +1 -1
- package/dist/WaitUntil.js +1 -1
- package/dist/WaitUntil.js.map +1 -1
- package/dist/Webhook.d.ts +72 -0
- package/dist/Webhook.d.ts.map +1 -0
- package/dist/Webhook.js +82 -0
- package/dist/Webhook.js.map +1 -0
- package/dist/alchemy/cloudflare/Email.d.ts +25 -0
- package/dist/alchemy/cloudflare/Email.d.ts.map +1 -0
- package/dist/alchemy/cloudflare/Email.js +117 -0
- package/dist/alchemy/cloudflare/Email.js.map +1 -0
- package/dist/alchemy/cloudflare/RateLimitDurableObject.d.ts +34 -0
- package/dist/alchemy/cloudflare/RateLimitDurableObject.d.ts.map +1 -0
- package/dist/alchemy/cloudflare/RateLimitDurableObject.js +30 -0
- package/dist/alchemy/cloudflare/RateLimitDurableObject.js.map +1 -0
- package/dist/cloudflare/Email.d.ts +25 -0
- package/dist/cloudflare/Email.d.ts.map +1 -0
- package/dist/cloudflare/Email.js +153 -0
- package/dist/cloudflare/Email.js.map +1 -0
- package/dist/cloudflare/RateLimitDurableObject.d.ts +50 -0
- package/dist/cloudflare/RateLimitDurableObject.d.ts.map +1 -0
- package/dist/cloudflare/RateLimitDurableObject.js +51 -0
- package/dist/cloudflare/RateLimitDurableObject.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/RateLimitDurableObject.d.ts +32 -0
- package/dist/internal/RateLimitDurableObject.d.ts.map +1 -0
- package/dist/internal/RateLimitDurableObject.js +80 -0
- package/dist/internal/RateLimitDurableObject.js.map +1 -0
- package/migrations/0002_auth_password.sql +25 -0
- package/migrations/0003_auth_login_approval_review.sql +22 -0
- package/migrations/0004_auth_trusted_device.sql +12 -0
- package/migrations/0005_auth_passkey.sql +17 -0
- package/migrations/0006_auth_totp_factor.sql +17 -0
- package/migrations/0007_auth_recovery_code.sql +13 -0
- package/migrations/0008_auth_api_key.sql +17 -0
- package/migrations/0009_auth_refresh_token.sql +21 -0
- package/migrations/0010_auth_jwt_revocation.sql +8 -0
- package/migrations/0011_auth_oauth_account.sql +16 -0
- package/migrations/0012_auth_oauth_provider_token_vault.sql +20 -0
- package/migrations/0013_auth_security_timeline.sql +16 -0
- package/package.json +146 -26
- package/dist/D1Kysely.d.ts +0 -40
- package/dist/D1Kysely.d.ts.map +0 -1
- package/dist/D1Kysely.js +0 -75
- package/dist/D1Kysely.js.map +0 -1
- package/dist/KyselyStorage.d.ts +0 -41
- package/dist/KyselyStorage.d.ts.map +0 -1
- package/dist/KyselyStorage.js +0 -176
- package/dist/KyselyStorage.js.map +0 -1
package/dist/AuthFlow.js
CHANGED
|
@@ -1,13 +1,665 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// oxlint-disable complexity
|
|
2
|
+
import { Context, Data, Duration, Effect, Layer, Option, Redacted, } from "effect";
|
|
3
|
+
import { AuthEmailVerificationSessionPolicyConfig } from "./AuthConfig.js";
|
|
4
|
+
import { Challenge } from "./Challenge.js";
|
|
5
|
+
import { Crypto } from "./Crypto.js";
|
|
6
|
+
import { AuthFlowId, ChallengeId, Email, UserId } from "./Identifiers.js";
|
|
7
|
+
import { currentUnixMillis } from "./Internal.js";
|
|
8
|
+
import { LoginApprovalFlow, LoginApprovalPolicy, LoginApprovalReview, makeLoginApprovalPolicy, unknownLoginDeviceContext, } from "./LoginApproval.js";
|
|
9
|
+
import { LoginNotification } from "./LoginNotification.js";
|
|
10
|
+
import { LoginRiskEngine, defaultLoginRiskContext, makeLoginRiskEngineNoop, } from "./LoginRisk.js";
|
|
11
|
+
import { Sessions, sessionRequestMetadataKey } from "./Sessions.js";
|
|
12
|
+
import { TrustedDevice } from "./TrustedDevice.js";
|
|
13
|
+
export const defaultLoginApprovalSameDeviceSecretBytes = 32;
|
|
14
|
+
export class EmailVerificationSessionPolicy extends Context.Service()("auth/EmailVerificationSessionPolicy") {
|
|
3
15
|
}
|
|
16
|
+
(function (EmailVerificationSessionPolicy) {
|
|
17
|
+
EmailVerificationSessionPolicy.make = (service) => EmailVerificationSessionPolicy.of(service);
|
|
18
|
+
})(EmailVerificationSessionPolicy || (EmailVerificationSessionPolicy = {}));
|
|
19
|
+
const addEmailVerificationRequirement = (requirements) => requirements?.includes("email_verification") === true
|
|
20
|
+
? requirements
|
|
21
|
+
: [...(requirements ?? []), "email_verification"];
|
|
22
|
+
const removeEmailVerificationRequirement = (requirements) => {
|
|
23
|
+
const filtered = requirements?.filter((requirement) => requirement !== "email_verification");
|
|
24
|
+
return filtered?.length === 0 ? undefined : filtered;
|
|
25
|
+
};
|
|
26
|
+
const withEmailVerified = (claims, emailVerified) => {
|
|
27
|
+
const { requirements: _requirements, ...rest } = claims ?? {};
|
|
28
|
+
const requirements = emailVerified
|
|
29
|
+
? removeEmailVerificationRequirement(claims?.requirements)
|
|
30
|
+
: claims?.requirements;
|
|
31
|
+
return {
|
|
32
|
+
...rest,
|
|
33
|
+
emailVerified,
|
|
34
|
+
...(requirements === undefined ? {} : { requirements }),
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
const withEmailVerificationRequirement = (claims) => ({
|
|
38
|
+
...claims,
|
|
39
|
+
emailVerified: false,
|
|
40
|
+
requirements: addEmailVerificationRequirement(claims?.requirements),
|
|
41
|
+
});
|
|
42
|
+
export const makeEmailVerificationSessionPolicy = (options = {}) => {
|
|
43
|
+
const mode = options.mode ?? "allow-unverified";
|
|
44
|
+
return EmailVerificationSessionPolicy.make({
|
|
45
|
+
decide: Effect.fn("auth.email_verification_session_policy.decide")(function* (input) {
|
|
46
|
+
const emailVerified = input.claims?.emailVerified;
|
|
47
|
+
if (emailVerified !== false) {
|
|
48
|
+
return {
|
|
49
|
+
_tag: "CreateSession",
|
|
50
|
+
claims: emailVerified === true
|
|
51
|
+
? withEmailVerified(input.claims, true)
|
|
52
|
+
: input.claims,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
switch (mode) {
|
|
56
|
+
case "allow-unverified": {
|
|
57
|
+
return {
|
|
58
|
+
_tag: "CreateSession",
|
|
59
|
+
claims: withEmailVerified(input.claims, false),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
case "limited-session": {
|
|
63
|
+
return {
|
|
64
|
+
_tag: "CreateSession",
|
|
65
|
+
claims: withEmailVerificationRequirement(input.claims),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
export const EmailVerificationSessionPolicyLive = (options) => Layer.succeed(EmailVerificationSessionPolicy)(makeEmailVerificationSessionPolicy(options));
|
|
73
|
+
export const AuthResult = Data.taggedEnum();
|
|
74
|
+
export class AuthFlowStateError extends Data.TaggedError("AuthFlowStateError") {
|
|
75
|
+
}
|
|
76
|
+
export class AuthFlowState extends Context.Service()("auth/AuthFlowState") {
|
|
77
|
+
}
|
|
78
|
+
(function (AuthFlowState) {
|
|
79
|
+
AuthFlowState.make = (service) => AuthFlowState.of(service);
|
|
80
|
+
})(AuthFlowState || (AuthFlowState = {}));
|
|
81
|
+
export const defaultAuthFlowStateTtl = Duration.minutes(10);
|
|
82
|
+
const flowStateMetadata = (input) => ({
|
|
83
|
+
...input.metadata,
|
|
84
|
+
method: input.method,
|
|
85
|
+
amr: input.amr,
|
|
86
|
+
aal: input.aal,
|
|
87
|
+
...(input.email === undefined ? {} : { email: input.email }),
|
|
88
|
+
...(input.intent === undefined ? {} : { intent: input.intent }),
|
|
89
|
+
...(input.device === undefined ? {} : { deviceStatus: input.device.status }),
|
|
90
|
+
...(input.claims === undefined ? {} : { claims: input.claims }),
|
|
91
|
+
});
|
|
92
|
+
const metadataString = (metadata, key) => {
|
|
93
|
+
const value = metadata?.[key];
|
|
94
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
95
|
+
};
|
|
96
|
+
const metadataStringArray = (metadata, key) => {
|
|
97
|
+
const value = metadata?.[key];
|
|
98
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string")
|
|
99
|
+
? value
|
|
100
|
+
: undefined;
|
|
101
|
+
};
|
|
102
|
+
const metadataClaims = (metadata) => {
|
|
103
|
+
const value = metadata?.claims;
|
|
104
|
+
if (value === undefined) {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
const shape = value;
|
|
111
|
+
const { requirements } = shape;
|
|
112
|
+
if (shape.emailVerified !== undefined &&
|
|
113
|
+
typeof shape.emailVerified !== "boolean") {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
if (requirements !== undefined &&
|
|
117
|
+
(!Array.isArray(requirements) ||
|
|
118
|
+
!requirements.every((item) => typeof item === "string"))) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
...(shape.emailVerified === undefined
|
|
123
|
+
? {}
|
|
124
|
+
: { emailVerified: shape.emailVerified }),
|
|
125
|
+
...(requirements === undefined ? {} : { requirements }),
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
const metadataAal = (metadata) => {
|
|
129
|
+
const value = metadataString(metadata, "aal");
|
|
130
|
+
return value === "aal1" || value === "aal2" || value === "aal3"
|
|
131
|
+
? value
|
|
132
|
+
: undefined;
|
|
133
|
+
};
|
|
134
|
+
const metadataIntent = (metadata) => {
|
|
135
|
+
const value = metadataString(metadata, "intent");
|
|
136
|
+
return value === "sign-in" || value === "sign-up" ? value : undefined;
|
|
137
|
+
};
|
|
138
|
+
const metadataDevice = (metadata) => {
|
|
139
|
+
const value = metadataString(metadata, "deviceStatus");
|
|
140
|
+
return value === "known" || value === "new" || value === "unknown"
|
|
141
|
+
? { status: value }
|
|
142
|
+
: undefined;
|
|
143
|
+
};
|
|
144
|
+
const appMetadata = (metadata) => {
|
|
145
|
+
if (metadata === undefined) {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
const { method: _method, amr: _amr, aal: _aal, email: _email, intent: _intent, deviceStatus: _deviceStatus, claims: _claims, ...rest } = metadata;
|
|
149
|
+
return Object.keys(rest).length === 0 ? undefined : rest;
|
|
150
|
+
};
|
|
151
|
+
const authFlowStateFromChallenge = (flowId, verified) => Effect.gen(function* () {
|
|
152
|
+
const method = metadataString(verified.metadata, "method");
|
|
153
|
+
const amr = metadataStringArray(verified.metadata, "amr");
|
|
154
|
+
const aal = metadataAal(verified.metadata);
|
|
155
|
+
const email = metadataString(verified.metadata, "email");
|
|
156
|
+
const intent = metadataIntent(verified.metadata);
|
|
157
|
+
const device = metadataDevice(verified.metadata);
|
|
158
|
+
const claims = metadataClaims(verified.metadata);
|
|
159
|
+
if (method === undefined) {
|
|
160
|
+
return yield* new AuthFlowStateError({
|
|
161
|
+
message: "Auth flow state is missing method metadata",
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
if (amr === undefined) {
|
|
165
|
+
return yield* new AuthFlowStateError({
|
|
166
|
+
message: "Auth flow state is missing AMR metadata",
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
if (aal === undefined) {
|
|
170
|
+
return yield* new AuthFlowStateError({
|
|
171
|
+
message: "Auth flow state is missing AAL metadata",
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
if (verified.metadata?.claims !== undefined && claims === undefined) {
|
|
175
|
+
return yield* new AuthFlowStateError({
|
|
176
|
+
message: "Auth flow state has invalid claims metadata",
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
flowId,
|
|
181
|
+
userId: UserId(verified.subject),
|
|
182
|
+
...(email === undefined ? {} : { email: Email(email) }),
|
|
183
|
+
...(intent === undefined ? {} : { intent }),
|
|
184
|
+
method,
|
|
185
|
+
amr,
|
|
186
|
+
aal,
|
|
187
|
+
...(device === undefined ? {} : { device }),
|
|
188
|
+
...(claims === undefined ? {} : { claims }),
|
|
189
|
+
metadata: appMetadata(verified.metadata),
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
export const makeAuthFlowState = (dependencies) => {
|
|
193
|
+
const ttl = dependencies.ttl ?? defaultAuthFlowStateTtl;
|
|
194
|
+
return AuthFlowState.make({
|
|
195
|
+
start: Effect.fn("auth.auth_flow_state.start")(function* (input) {
|
|
196
|
+
const issued = yield* dependencies.challenge
|
|
197
|
+
.issue({
|
|
198
|
+
type: "auth-flow-state",
|
|
199
|
+
subject: input.userId,
|
|
200
|
+
ttl: input.ttl ?? ttl,
|
|
201
|
+
secret: input.secret,
|
|
202
|
+
metadata: flowStateMetadata(input),
|
|
203
|
+
})
|
|
204
|
+
.pipe(Effect.mapError((cause) => new AuthFlowStateError({
|
|
205
|
+
message: "Failed to start auth flow state",
|
|
206
|
+
cause,
|
|
207
|
+
})));
|
|
208
|
+
return {
|
|
209
|
+
flowId: AuthFlowId(issued.id),
|
|
210
|
+
userId: input.userId,
|
|
211
|
+
expiresAt: issued.expiresAt,
|
|
212
|
+
};
|
|
213
|
+
}),
|
|
214
|
+
inspect: Effect.fn("auth.auth_flow_state.inspect")(function* (flowId, secret) {
|
|
215
|
+
const inspected = yield* dependencies.challenge
|
|
216
|
+
.inspect({
|
|
217
|
+
challengeId: ChallengeId(flowId),
|
|
218
|
+
type: "auth-flow-state",
|
|
219
|
+
secret,
|
|
220
|
+
})
|
|
221
|
+
.pipe(Effect.mapError((cause) => new AuthFlowStateError({
|
|
222
|
+
message: "Invalid auth flow state",
|
|
223
|
+
cause,
|
|
224
|
+
})));
|
|
225
|
+
const state = yield* authFlowStateFromChallenge(flowId, inspected);
|
|
226
|
+
return {
|
|
227
|
+
...state,
|
|
228
|
+
expiresAt: inspected.expiresAt,
|
|
229
|
+
};
|
|
230
|
+
}),
|
|
231
|
+
consume: Effect.fn("auth.auth_flow_state.consume")(function* (flowId, secret) {
|
|
232
|
+
const verified = yield* dependencies.challenge
|
|
233
|
+
.verify({
|
|
234
|
+
challengeId: ChallengeId(flowId),
|
|
235
|
+
type: "auth-flow-state",
|
|
236
|
+
secret,
|
|
237
|
+
})
|
|
238
|
+
.pipe(Effect.mapError((cause) => new AuthFlowStateError({
|
|
239
|
+
message: "Invalid auth flow state",
|
|
240
|
+
cause,
|
|
241
|
+
})));
|
|
242
|
+
return yield* authFlowStateFromChallenge(flowId, verified);
|
|
243
|
+
}),
|
|
244
|
+
});
|
|
245
|
+
};
|
|
246
|
+
export const AuthFlowStateLive = (options) => Layer.effect(AuthFlowState)(Effect.gen(function* () {
|
|
247
|
+
const challenge = yield* Challenge;
|
|
248
|
+
return makeAuthFlowState({
|
|
249
|
+
...options,
|
|
250
|
+
challenge,
|
|
251
|
+
});
|
|
252
|
+
}));
|
|
4
253
|
export class AuthFlow extends Context.Service()("auth/AuthFlow") {
|
|
5
254
|
}
|
|
6
255
|
(function (AuthFlow) {
|
|
7
256
|
AuthFlow.make = (service) => AuthFlow.of(service);
|
|
8
257
|
})(AuthFlow || (AuthFlow = {}));
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
258
|
+
export class LoginApprovalFinalize extends Context.Service()("auth/LoginApprovalFinalize") {
|
|
259
|
+
}
|
|
260
|
+
(function (LoginApprovalFinalize) {
|
|
261
|
+
LoginApprovalFinalize.make = (service) => LoginApprovalFinalize.of(service);
|
|
262
|
+
})(LoginApprovalFinalize || (LoginApprovalFinalize = {}));
|
|
263
|
+
const loginApprovalFinalizeDenied = (reason) => AuthResult.PolicyDenied({ reason });
|
|
264
|
+
export const makeLoginApprovalFinalize = (dependencies) => LoginApprovalFinalize.make({
|
|
265
|
+
complete: Effect.fn("auth.login_approval_finalize.complete")(function* (input) {
|
|
266
|
+
const recordOption = yield* dependencies.loginApprovalReview.status({
|
|
267
|
+
approvalChallengeId: input.approvalChallengeId,
|
|
268
|
+
});
|
|
269
|
+
if (Option.isNone(recordOption)) {
|
|
270
|
+
return loginApprovalFinalizeDenied("Login approval review was not found");
|
|
271
|
+
}
|
|
272
|
+
const record = recordOption.value;
|
|
273
|
+
if (record.flowId !== input.flowId) {
|
|
274
|
+
return loginApprovalFinalizeDenied("Invalid login approval review");
|
|
275
|
+
}
|
|
276
|
+
const now = yield* currentUnixMillis;
|
|
277
|
+
if (Number(record.expiresAt) <= Number(now)) {
|
|
278
|
+
return loginApprovalFinalizeDenied("Login approval expired");
|
|
279
|
+
}
|
|
280
|
+
if (record.status === "pending") {
|
|
281
|
+
return loginApprovalFinalizeDenied("Login approval is pending");
|
|
282
|
+
}
|
|
283
|
+
if (record.status === "denied") {
|
|
284
|
+
return loginApprovalFinalizeDenied(record.deniedReason ?? "Login approval denied");
|
|
285
|
+
}
|
|
286
|
+
const consumed = yield* dependencies.authFlowState.consume(input.flowId, input.sameDeviceSecret);
|
|
287
|
+
if (consumed.userId !== record.userId) {
|
|
288
|
+
return loginApprovalFinalizeDenied("Invalid login approval review");
|
|
289
|
+
}
|
|
290
|
+
return yield* dependencies.authFlow.complete(consumed);
|
|
291
|
+
}),
|
|
292
|
+
});
|
|
293
|
+
export const LoginApprovalFinalizeLive = Layer.effect(LoginApprovalFinalize)(Effect.gen(function* () {
|
|
294
|
+
const loginApprovalReview = yield* LoginApprovalReview;
|
|
295
|
+
const authFlowState = yield* AuthFlowState;
|
|
296
|
+
const authFlow = yield* AuthFlow;
|
|
297
|
+
return makeLoginApprovalFinalize({
|
|
298
|
+
loginApprovalReview,
|
|
299
|
+
authFlowState,
|
|
300
|
+
authFlow,
|
|
301
|
+
});
|
|
302
|
+
}));
|
|
303
|
+
const loginApprovalFlowIdMetadata = (flowId, metadata) => ({
|
|
304
|
+
...metadata,
|
|
305
|
+
flowId,
|
|
306
|
+
});
|
|
307
|
+
const sessionMetadata = (metadata, request) => request === undefined
|
|
308
|
+
? metadata
|
|
309
|
+
: {
|
|
310
|
+
...metadata,
|
|
311
|
+
[sessionRequestMetadataKey]: request,
|
|
312
|
+
};
|
|
313
|
+
const mergeMetadata = (left, right) => {
|
|
314
|
+
if (left === undefined) {
|
|
315
|
+
return right;
|
|
316
|
+
}
|
|
317
|
+
if (right === undefined) {
|
|
318
|
+
return left;
|
|
319
|
+
}
|
|
320
|
+
return { ...left, ...right };
|
|
321
|
+
};
|
|
322
|
+
const aalRank = {
|
|
323
|
+
aal1: 1,
|
|
324
|
+
aal2: 2,
|
|
325
|
+
aal3: 3,
|
|
326
|
+
};
|
|
327
|
+
const strongerAal = (left, right) => (aalRank[left] >= aalRank[right] ? left : right);
|
|
328
|
+
const authMethodReference = (method) => {
|
|
329
|
+
switch (method) {
|
|
330
|
+
case "password": {
|
|
331
|
+
return "pwd";
|
|
332
|
+
}
|
|
333
|
+
case "email-otp": {
|
|
334
|
+
return "otp";
|
|
335
|
+
}
|
|
336
|
+
case "magic-link": {
|
|
337
|
+
return "magic_link";
|
|
338
|
+
}
|
|
339
|
+
case "backup-code": {
|
|
340
|
+
return "backup_code";
|
|
341
|
+
}
|
|
342
|
+
default: {
|
|
343
|
+
return method;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
const appendAuthMethods = (current, methods) => {
|
|
348
|
+
const next = [...current];
|
|
349
|
+
for (const method of methods) {
|
|
350
|
+
if (!next.includes(method)) {
|
|
351
|
+
next.push(method);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return next;
|
|
355
|
+
};
|
|
356
|
+
const loginNotificationSession = (session) => ({
|
|
357
|
+
sessionId: session.sessionId,
|
|
358
|
+
userId: session.userId,
|
|
359
|
+
authTime: session.authTime,
|
|
360
|
+
expiresAt: session.expiresAt,
|
|
361
|
+
aal: session.aal,
|
|
362
|
+
amr: session.amr,
|
|
363
|
+
...(session.claims === undefined ? {} : { claims: session.claims }),
|
|
364
|
+
});
|
|
365
|
+
const loginRequestContextFromMetadata = (metadata) => {
|
|
366
|
+
const value = metadata?.[sessionRequestMetadataKey];
|
|
367
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
368
|
+
? value
|
|
369
|
+
: undefined;
|
|
370
|
+
};
|
|
371
|
+
const loginApprovalNotConfigured = () => AuthResult.PolicyDenied({
|
|
372
|
+
reason: "Login approval is not configured",
|
|
373
|
+
});
|
|
374
|
+
const loginApprovalEmailMissing = () => AuthResult.PolicyDenied({
|
|
375
|
+
reason: "Login approval requires an email address",
|
|
376
|
+
});
|
|
377
|
+
const loginApprovalSameDeviceNotConfigured = () => AuthResult.PolicyDenied({
|
|
378
|
+
reason: "Login approval same-device binding is not configured",
|
|
379
|
+
});
|
|
380
|
+
const loginApprovalReviewNotConfigured = () => AuthResult.PolicyDenied({
|
|
381
|
+
reason: "Login approval review is not configured",
|
|
382
|
+
});
|
|
383
|
+
const mfaFlowStateNotConfigured = () => AuthResult.PolicyDenied({
|
|
384
|
+
reason: "MFA flow state is not configured",
|
|
385
|
+
});
|
|
386
|
+
const mfaFlowUserMismatch = () => AuthResult.PolicyDenied({
|
|
387
|
+
reason: "MFA flow user mismatch",
|
|
388
|
+
});
|
|
389
|
+
const optionalLoginApprovalDependencies = Effect.gen(function* () {
|
|
390
|
+
const loginApprovalPolicy = yield* Effect.serviceOption(LoginApprovalPolicy);
|
|
391
|
+
const loginApprovalFlow = yield* Effect.serviceOption(LoginApprovalFlow);
|
|
392
|
+
const authFlowState = yield* Effect.serviceOption(AuthFlowState);
|
|
393
|
+
const crypto = yield* Effect.serviceOption(Crypto);
|
|
394
|
+
const trustedDevice = yield* Effect.serviceOption(TrustedDevice);
|
|
395
|
+
const loginRiskEngine = yield* Effect.serviceOption(LoginRiskEngine);
|
|
396
|
+
const loginApprovalReview = yield* Effect.serviceOption(LoginApprovalReview);
|
|
397
|
+
const loginNotification = yield* Effect.serviceOption(LoginNotification);
|
|
398
|
+
return {
|
|
399
|
+
...(Option.isSome(loginApprovalPolicy)
|
|
400
|
+
? { loginApprovalPolicy: loginApprovalPolicy.value }
|
|
401
|
+
: {}),
|
|
402
|
+
...(Option.isSome(loginApprovalFlow)
|
|
403
|
+
? { loginApprovalFlow: loginApprovalFlow.value }
|
|
404
|
+
: {}),
|
|
405
|
+
...(Option.isSome(authFlowState)
|
|
406
|
+
? { authFlowState: authFlowState.value }
|
|
407
|
+
: {}),
|
|
408
|
+
...(Option.isSome(crypto) ? { crypto: crypto.value } : {}),
|
|
409
|
+
...(Option.isSome(trustedDevice)
|
|
410
|
+
? { trustedDevice: trustedDevice.value }
|
|
411
|
+
: {}),
|
|
412
|
+
...(Option.isSome(loginRiskEngine)
|
|
413
|
+
? { loginRiskEngine: loginRiskEngine.value }
|
|
414
|
+
: {}),
|
|
415
|
+
...(Option.isSome(loginApprovalReview)
|
|
416
|
+
? { loginApprovalReview: loginApprovalReview.value }
|
|
417
|
+
: {}),
|
|
418
|
+
...(Option.isSome(loginNotification)
|
|
419
|
+
? { loginNotification: loginNotification.value }
|
|
420
|
+
: {}),
|
|
421
|
+
};
|
|
12
422
|
});
|
|
423
|
+
export const makeAuthFlow = (dependencies) => {
|
|
424
|
+
const emailVerificationSessionPolicy = dependencies.emailVerificationSessionPolicy ??
|
|
425
|
+
makeEmailVerificationSessionPolicy();
|
|
426
|
+
const loginApprovalPolicy = dependencies.loginApprovalPolicy ?? makeLoginApprovalPolicy();
|
|
427
|
+
const loginRiskEngine = dependencies.loginRiskEngine ?? makeLoginRiskEngineNoop();
|
|
428
|
+
const loginApprovalSameDeviceSecretBytes = dependencies.loginApprovalSameDeviceSecretBytes ??
|
|
429
|
+
defaultLoginApprovalSameDeviceSecretBytes;
|
|
430
|
+
const resolveDevice = (input) => {
|
|
431
|
+
if (input.device !== undefined) {
|
|
432
|
+
return Effect.succeed(input.device);
|
|
433
|
+
}
|
|
434
|
+
if (dependencies.trustedDevice === undefined) {
|
|
435
|
+
return Effect.succeed(unknownLoginDeviceContext);
|
|
436
|
+
}
|
|
437
|
+
return dependencies.trustedDevice.status({
|
|
438
|
+
userId: input.userId,
|
|
439
|
+
token: input.trustedDeviceToken,
|
|
440
|
+
});
|
|
441
|
+
};
|
|
442
|
+
const notifyLogin = (input, session) => dependencies.loginNotification === undefined
|
|
443
|
+
? Effect.void
|
|
444
|
+
: dependencies.loginNotification
|
|
445
|
+
.notify({
|
|
446
|
+
userId: input.userId,
|
|
447
|
+
...(input.email === undefined ? {} : { email: input.email }),
|
|
448
|
+
...(input.intent === undefined ? {} : { intent: input.intent }),
|
|
449
|
+
method: input.method,
|
|
450
|
+
amr: input.amr,
|
|
451
|
+
aal: input.aal,
|
|
452
|
+
session: loginNotificationSession(session),
|
|
453
|
+
device: input.device ?? unknownLoginDeviceContext,
|
|
454
|
+
risk: input.risk ?? defaultLoginRiskContext,
|
|
455
|
+
request: input.request ?? loginRequestContextFromMetadata(input.metadata),
|
|
456
|
+
...(input.metadata === undefined ? {} : { metadata: input.metadata }),
|
|
457
|
+
})
|
|
458
|
+
.pipe(Effect.catchCause(() => Effect.void));
|
|
459
|
+
const complete = Effect.fn("auth.auth_flow.complete")(function* (input) {
|
|
460
|
+
yield* Effect.annotateCurrentSpan({
|
|
461
|
+
"auth.method": input.method,
|
|
462
|
+
"auth.aal": input.aal,
|
|
463
|
+
"auth.amr_count": input.amr.length,
|
|
464
|
+
});
|
|
465
|
+
const session = yield* dependencies.sessions.create({
|
|
466
|
+
userId: input.userId,
|
|
467
|
+
method: input.method,
|
|
468
|
+
amr: input.amr,
|
|
469
|
+
aal: input.aal,
|
|
470
|
+
claims: input.claims,
|
|
471
|
+
...(input.mfaVerifiedAt === undefined
|
|
472
|
+
? {}
|
|
473
|
+
: { mfaVerifiedAt: input.mfaVerifiedAt }),
|
|
474
|
+
metadata: input.metadata,
|
|
475
|
+
});
|
|
476
|
+
yield* notifyLogin(input, session);
|
|
477
|
+
yield* Effect.annotateCurrentSpan("auth.result", "Authenticated");
|
|
478
|
+
return AuthResult.Authenticated({ session });
|
|
479
|
+
});
|
|
480
|
+
const completePrimaryFactor = Effect.fn("auth.auth_flow.complete_primary_factor")(function* (input) {
|
|
481
|
+
const emailVerificationDecision = yield* emailVerificationSessionPolicy.decide({
|
|
482
|
+
claims: input.claims,
|
|
483
|
+
});
|
|
484
|
+
const metadata = sessionMetadata(input.metadata, input.request);
|
|
485
|
+
const device = yield* resolveDevice(input);
|
|
486
|
+
const risk = yield* loginRiskEngine.assess({
|
|
487
|
+
userId: input.userId,
|
|
488
|
+
...(input.email === undefined ? {} : { email: input.email }),
|
|
489
|
+
emailVerified: emailVerificationDecision.claims?.emailVerified,
|
|
490
|
+
method: input.method,
|
|
491
|
+
amr: input.amr,
|
|
492
|
+
aal: input.aal,
|
|
493
|
+
device,
|
|
494
|
+
...(input.request === undefined ? {} : { request: input.request }),
|
|
495
|
+
...(metadata === undefined ? {} : { metadata }),
|
|
496
|
+
});
|
|
497
|
+
const loginApprovalDecision = yield* loginApprovalPolicy.decide({
|
|
498
|
+
...(input.intent === undefined ? {} : { intent: input.intent }),
|
|
499
|
+
userId: input.userId,
|
|
500
|
+
...(input.email === undefined ? {} : { email: input.email }),
|
|
501
|
+
emailVerified: emailVerificationDecision.claims?.emailVerified,
|
|
502
|
+
method: input.method,
|
|
503
|
+
amr: input.amr,
|
|
504
|
+
aal: input.aal,
|
|
505
|
+
device,
|
|
506
|
+
risk,
|
|
507
|
+
...(metadata === undefined ? {} : { metadata }),
|
|
508
|
+
});
|
|
509
|
+
if (loginApprovalDecision._tag === "RequiresApproval") {
|
|
510
|
+
if (loginApprovalDecision.channel === "email" &&
|
|
511
|
+
input.email === undefined) {
|
|
512
|
+
yield* Effect.annotateCurrentSpan("auth.result", "PolicyDenied");
|
|
513
|
+
return loginApprovalEmailMissing();
|
|
514
|
+
}
|
|
515
|
+
if (dependencies.authFlowState === undefined ||
|
|
516
|
+
dependencies.loginApprovalFlow === undefined) {
|
|
517
|
+
yield* Effect.annotateCurrentSpan("auth.result", "PolicyDenied");
|
|
518
|
+
return loginApprovalNotConfigured();
|
|
519
|
+
}
|
|
520
|
+
const { loginApprovalReview } = dependencies;
|
|
521
|
+
if (loginApprovalDecision.sessionBinding === "none" &&
|
|
522
|
+
loginApprovalReview === undefined) {
|
|
523
|
+
yield* Effect.annotateCurrentSpan("auth.result", "PolicyDenied");
|
|
524
|
+
return loginApprovalReviewNotConfigured();
|
|
525
|
+
}
|
|
526
|
+
let sameDeviceSecret;
|
|
527
|
+
if (loginApprovalDecision.sameDeviceRequired) {
|
|
528
|
+
if (dependencies.crypto === undefined) {
|
|
529
|
+
yield* Effect.annotateCurrentSpan("auth.result", "PolicyDenied");
|
|
530
|
+
return loginApprovalSameDeviceNotConfigured();
|
|
531
|
+
}
|
|
532
|
+
sameDeviceSecret = Redacted.make(yield* dependencies.crypto
|
|
533
|
+
.randomToken(loginApprovalSameDeviceSecretBytes)
|
|
534
|
+
.pipe(Effect.mapError((cause) => new AuthFlowStateError({
|
|
535
|
+
message: "Failed to create login approval same-device binding",
|
|
536
|
+
cause,
|
|
537
|
+
}))));
|
|
538
|
+
}
|
|
539
|
+
const claims = loginApprovalDecision.markEmailVerified === true
|
|
540
|
+
? withEmailVerified(emailVerificationDecision.claims, true)
|
|
541
|
+
: emailVerificationDecision.claims;
|
|
542
|
+
const flowState = yield* dependencies.authFlowState.start({
|
|
543
|
+
userId: input.userId,
|
|
544
|
+
method: input.method,
|
|
545
|
+
amr: input.amr,
|
|
546
|
+
aal: input.aal,
|
|
547
|
+
...(input.email === undefined ? {} : { email: input.email }),
|
|
548
|
+
...(input.intent === undefined ? {} : { intent: input.intent }),
|
|
549
|
+
device,
|
|
550
|
+
claims,
|
|
551
|
+
secret: sameDeviceSecret,
|
|
552
|
+
...(metadata === undefined ? {} : { metadata }),
|
|
553
|
+
});
|
|
554
|
+
const approval = yield* dependencies.loginApprovalFlow.start({
|
|
555
|
+
userId: input.userId,
|
|
556
|
+
...(input.email === undefined ? {} : { email: input.email }),
|
|
557
|
+
channel: loginApprovalDecision.channel,
|
|
558
|
+
reason: loginApprovalDecision.reason,
|
|
559
|
+
sessionBinding: loginApprovalDecision.sessionBinding,
|
|
560
|
+
sameDeviceRequired: loginApprovalDecision.sameDeviceRequired,
|
|
561
|
+
...(loginApprovalDecision.markEmailVerified === true
|
|
562
|
+
? { markEmailVerified: true }
|
|
563
|
+
: {}),
|
|
564
|
+
risk,
|
|
565
|
+
metadata: loginApprovalFlowIdMetadata(flowState.flowId, metadata),
|
|
566
|
+
});
|
|
567
|
+
if (approval.sessionBinding === "none" &&
|
|
568
|
+
loginApprovalReview !== undefined) {
|
|
569
|
+
yield* loginApprovalReview.start({
|
|
570
|
+
approval,
|
|
571
|
+
flowId: flowState.flowId,
|
|
572
|
+
metadata,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
yield* Effect.annotateCurrentSpan({
|
|
576
|
+
"auth.result": "RequiresLoginApproval",
|
|
577
|
+
"auth.login_approval.reason": approval.reason,
|
|
578
|
+
"auth.login_approval.session_binding": approval.sessionBinding,
|
|
579
|
+
"auth.login_approval.same_device_required": approval.sameDeviceRequired,
|
|
580
|
+
});
|
|
581
|
+
return AuthResult.RequiresLoginApproval({
|
|
582
|
+
flowId: flowState.flowId,
|
|
583
|
+
approvalChallengeId: approval.challengeId,
|
|
584
|
+
channel: approval.channel,
|
|
585
|
+
sessionBinding: approval.sessionBinding,
|
|
586
|
+
sameDeviceRequired: approval.sameDeviceRequired,
|
|
587
|
+
reason: approval.reason,
|
|
588
|
+
expiresAt: flowState.expiresAt,
|
|
589
|
+
...(sameDeviceSecret === undefined ? {} : { sameDeviceSecret }),
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
return yield* complete({
|
|
593
|
+
userId: input.userId,
|
|
594
|
+
...(input.email === undefined ? {} : { email: input.email }),
|
|
595
|
+
...(input.intent === undefined ? {} : { intent: input.intent }),
|
|
596
|
+
method: input.method,
|
|
597
|
+
amr: input.amr,
|
|
598
|
+
aal: input.aal,
|
|
599
|
+
device,
|
|
600
|
+
...(input.request === undefined ? {} : { request: input.request }),
|
|
601
|
+
risk,
|
|
602
|
+
claims: emailVerificationDecision.claims,
|
|
603
|
+
metadata,
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
const completeMfa = Effect.fn("auth.auth_flow.complete_mfa")(function* (input) {
|
|
607
|
+
if (dependencies.authFlowState === undefined) {
|
|
608
|
+
yield* Effect.annotateCurrentSpan("auth.result", "PolicyDenied");
|
|
609
|
+
return mfaFlowStateNotConfigured();
|
|
610
|
+
}
|
|
611
|
+
const consumed = yield* dependencies.authFlowState.consume(input.flowId, input.secret);
|
|
612
|
+
if (input.userId !== undefined && consumed.userId !== input.userId) {
|
|
613
|
+
yield* Effect.annotateCurrentSpan("auth.result", "PolicyDenied");
|
|
614
|
+
return mfaFlowUserMismatch();
|
|
615
|
+
}
|
|
616
|
+
const mfaVerifiedAt = input.now ?? (yield* currentUnixMillis);
|
|
617
|
+
const amr = appendAuthMethods(consumed.amr, input.amr ?? [authMethodReference(input.method)]);
|
|
618
|
+
const aal = strongerAal(consumed.aal, input.aal ?? "aal2");
|
|
619
|
+
const metadata = mergeMetadata(consumed.metadata, input.metadata);
|
|
620
|
+
yield* Effect.annotateCurrentSpan({
|
|
621
|
+
"auth.result": "Authenticated",
|
|
622
|
+
"auth.mfa.method": input.method,
|
|
623
|
+
"auth.mfa.aal": aal,
|
|
624
|
+
"auth.mfa.amr_count": amr.length,
|
|
625
|
+
});
|
|
626
|
+
return yield* complete({
|
|
627
|
+
userId: consumed.userId,
|
|
628
|
+
...(consumed.email === undefined ? {} : { email: consumed.email }),
|
|
629
|
+
...(consumed.intent === undefined ? {} : { intent: consumed.intent }),
|
|
630
|
+
method: consumed.method,
|
|
631
|
+
amr,
|
|
632
|
+
aal,
|
|
633
|
+
...(consumed.device === undefined ? {} : { device: consumed.device }),
|
|
634
|
+
claims: consumed.claims,
|
|
635
|
+
mfaVerifiedAt,
|
|
636
|
+
metadata,
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
return AuthFlow.make({
|
|
640
|
+
startPrimaryFactor: completePrimaryFactor,
|
|
641
|
+
completePrimaryFactor,
|
|
642
|
+
completeMfa,
|
|
643
|
+
complete,
|
|
644
|
+
});
|
|
645
|
+
};
|
|
646
|
+
export const AuthFlowLive = Layer.effect(AuthFlow)(Effect.gen(function* () {
|
|
647
|
+
const sessions = yield* Sessions;
|
|
648
|
+
const emailVerificationSessionPolicyOptions = yield* AuthEmailVerificationSessionPolicyConfig;
|
|
649
|
+
const loginApprovalDependencies = yield* optionalLoginApprovalDependencies;
|
|
650
|
+
return makeAuthFlow({
|
|
651
|
+
sessions,
|
|
652
|
+
emailVerificationSessionPolicy: makeEmailVerificationSessionPolicy(emailVerificationSessionPolicyOptions),
|
|
653
|
+
...loginApprovalDependencies,
|
|
654
|
+
});
|
|
655
|
+
}));
|
|
656
|
+
export const AuthFlowLiveWithEmailVerificationPolicy = (options) => Layer.effect(AuthFlow)(Effect.gen(function* () {
|
|
657
|
+
const sessions = yield* Sessions;
|
|
658
|
+
const loginApprovalDependencies = yield* optionalLoginApprovalDependencies;
|
|
659
|
+
return makeAuthFlow({
|
|
660
|
+
sessions,
|
|
661
|
+
emailVerificationSessionPolicy: makeEmailVerificationSessionPolicy(options),
|
|
662
|
+
...loginApprovalDependencies,
|
|
663
|
+
});
|
|
664
|
+
}));
|
|
13
665
|
//# sourceMappingURL=AuthFlow.js.map
|