@effect-auth/core 0.1.0-alpha.3 → 0.1.0-alpha.4
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 +148 -13
- 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 +204 -1
- package/dist/AuditLog.d.ts.map +1 -1
- package/dist/AuditLog.js +170 -0
- package/dist/AuditLog.js.map +1 -1
- package/dist/AuthConfig.d.ts +0 -9
- package/dist/AuthConfig.d.ts.map +1 -1
- package/dist/AuthConfig.js +1 -14
- package/dist/AuthConfig.js.map +1 -1
- package/dist/AuthFlow.d.ts +220 -51
- package/dist/AuthFlow.d.ts.map +1 -1
- package/dist/AuthFlow.js +550 -22
- package/dist/AuthFlow.js.map +1 -1
- package/dist/AuthKernel.d.ts +1 -1
- package/dist/Challenge.d.ts +5 -1
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js +58 -48
- package/dist/Challenge.js.map +1 -1
- package/dist/Client.d.ts +242 -1
- package/dist/Client.d.ts.map +1 -1
- package/dist/Client.js +489 -1
- package/dist/Client.js.map +1 -1
- package/dist/D1Kysely.d.ts +1 -1
- package/dist/D1Kysely.d.ts.map +1 -1
- package/dist/EffectQbSqliteStorage.d.ts +115 -3
- package/dist/EffectQbSqliteStorage.d.ts.map +1 -1
- package/dist/EffectQbSqliteStorage.js +314 -4
- package/dist/EffectQbSqliteStorage.js.map +1 -1
- package/dist/EmailOtp.d.ts +5 -2
- package/dist/EmailOtp.d.ts.map +1 -1
- package/dist/EmailOtp.js +11 -4
- package/dist/EmailOtp.js.map +1 -1
- package/dist/EmailVerification.d.ts +2 -2
- package/dist/EmailVerification.d.ts.map +1 -1
- package/dist/EmailVerification.js +2 -3
- package/dist/EmailVerification.js.map +1 -1
- package/dist/HttpApi/Api.d.ts +1161 -19
- package/dist/HttpApi/Api.d.ts.map +1 -1
- package/dist/HttpApi/Api.js +1433 -20
- package/dist/HttpApi/Api.js.map +1 -1
- package/dist/HttpApi/Endpoints.d.ts +611 -0
- package/dist/HttpApi/Endpoints.d.ts.map +1 -1
- package/dist/HttpApi/Endpoints.js +242 -1
- package/dist/HttpApi/Endpoints.js.map +1 -1
- package/dist/HttpApi/Schemas.d.ts +704 -0
- package/dist/HttpApi/Schemas.d.ts.map +1 -1
- package/dist/HttpApi/Schemas.js +435 -1
- package/dist/HttpApi/Schemas.js.map +1 -1
- package/dist/HttpApi/Security.d.ts +13 -1
- package/dist/HttpApi/Security.d.ts.map +1 -1
- package/dist/HttpApi/Security.js +107 -16
- package/dist/HttpApi/Security.js.map +1 -1
- package/dist/HttpApi/Transport.d.ts +36 -2
- package/dist/HttpApi/Transport.d.ts.map +1 -1
- package/dist/HttpApi/Transport.js +151 -9
- package/dist/HttpApi/Transport.js.map +1 -1
- 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/KyselyStorage.d.ts +134 -1
- package/dist/KyselyStorage.d.ts.map +1 -1
- package/dist/KyselyStorage.js +918 -4
- package/dist/KyselyStorage.js.map +1 -1
- 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 +33 -2
- package/dist/Mailer.d.ts.map +1 -1
- package/dist/Mailer.js +107 -0
- package/dist/Mailer.js.map +1 -1
- 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/Password.d.ts +138 -8
- package/dist/Password.d.ts.map +1 -1
- package/dist/Password.js +397 -8
- package/dist/Password.js.map +1 -1
- 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/Sessions.d.ts +52 -2
- package/dist/Sessions.d.ts.map +1 -1
- package/dist/Sessions.js +123 -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 +14 -1
- package/dist/Storage.d.ts.map +1 -1
- package/dist/Storage.js.map +1 -1
- package/dist/StorageMigrations.d.ts +9 -1
- package/dist/StorageMigrations.d.ts.map +1 -1
- package/dist/StorageMigrations.js +159 -0
- package/dist/StorageMigrations.js.map +1 -1
- package/dist/Testing.d.ts +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 +115 -0
- package/dist/TrustedDevice.d.ts.map +1 -0
- package/dist/TrustedDevice.js +222 -0
- package/dist/TrustedDevice.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/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/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- 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/package.json +57 -1
package/README.md
CHANGED
|
@@ -58,6 +58,21 @@ const canEditAndNotSuspended = Policy.all(
|
|
|
58
58
|
);
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
+
Step-up gates live in `@effect-auth/core/StepUp` and can be converted to policies when a product action requires stronger or fresher auth:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { Duration } from "effect";
|
|
65
|
+
import * as Policy from "@effect-auth/core/Policy";
|
|
66
|
+
import * as StepUp from "@effect-auth/core/StepUp";
|
|
67
|
+
|
|
68
|
+
const canGenerateApiKey = StepUp.toPolicy({
|
|
69
|
+
aal: "aal2",
|
|
70
|
+
maxAge: Duration.minutes(15),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const generateApiKey = createApiKey.pipe(Policy.require(canGenerateApiKey));
|
|
74
|
+
```
|
|
75
|
+
|
|
61
76
|
## Rate Limiter
|
|
62
77
|
|
|
63
78
|
`RateLimiter` is a small auth-oriented wrapper around Effect's persistent rate limiter. It keeps the public auth API focused on policies and safe keys while delegating fixed-window/token-bucket mechanics to `effect/unstable/persistence`.
|
|
@@ -288,7 +303,7 @@ const PasswordHashingLive = Layer.mergeAll(
|
|
|
288
303
|
|
|
289
304
|
## HTTP API
|
|
290
305
|
|
|
291
|
-
`@effect-auth/core/HttpApi` contains the first Effect v4 HttpApi contract for
|
|
306
|
+
`@effect-auth/core/HttpApi` contains the first Effect v4 HttpApi contract for the built-in auth surface. It exports `AuthApi`, module-owned endpoint groups, and handler layers for the core services.
|
|
292
307
|
|
|
293
308
|
```ts
|
|
294
309
|
import { Layer } from "effect";
|
|
@@ -306,7 +321,19 @@ export const AuthRoutesLive = HttpRouter.serve(
|
|
|
306
321
|
);
|
|
307
322
|
```
|
|
308
323
|
|
|
309
|
-
The contract currently includes
|
|
324
|
+
The contract currently includes these endpoint groups:
|
|
325
|
+
|
|
326
|
+
| Group | Endpoints |
|
|
327
|
+
| --- | --- |
|
|
328
|
+
| Password | `POST /auth/password/sign-in`, `POST /auth/password/sign-up`, `POST /auth/password/reset/start`, `POST /auth/password/reset/verify`, `POST /auth/password/set`, `POST /auth/password/change`. |
|
|
329
|
+
| Sessions | `GET /auth/session`, `POST /auth/session/refresh`, `POST /auth/logout`, `GET /auth/sessions`, `POST /auth/sessions/revoke`, `POST /auth/sessions/revoke-others`. |
|
|
330
|
+
| Email verification | `POST /auth/email-verification/start`, `POST /auth/email-verification/verify`. |
|
|
331
|
+
| Email OTP | `POST /auth/email-otp/start`, `POST /auth/email-otp/verify`. |
|
|
332
|
+
| Magic link | `POST /auth/magic-link/start`, `POST /auth/magic-link/verify`. |
|
|
333
|
+
| Login approval | `POST /auth/login-approval/approve`, `POST /auth/login-approval/status`, `POST /auth/login-approval/finalize`. |
|
|
334
|
+
| Security | `POST /auth/security/login/report`. |
|
|
335
|
+
|
|
336
|
+
`AuthHttpApiLive` wires the full built-in surface. Apps can own the final HTTP contract and reuse individual endpoints, handlers, or services when a feature needs app-specific payloads or responses.
|
|
310
337
|
|
|
311
338
|
`Authenticated` results set the configured session cookie and never include the opaque session token in JSON bodies. Invalid credentials and disabled accounts map to the same `401 invalid_credentials` response by default. Duplicate password sign-up emails fail as `EmailAlreadyRegisteredError` in the domain layer and map to a `409 email_already_registered` HTTP API error.
|
|
312
339
|
|
|
@@ -381,7 +408,7 @@ export const AuthHttpApiLayer = AuthHttpApiBareLive.pipe(
|
|
|
381
408
|
|
|
382
409
|
`EmailVerificationApi` exposes `start` and `verify` endpoints. `start` calls `EmailVerificationFlow` to issue and send a verification challenge through the app-owned `EmailDelivery` adapter. `verify` calls `EmailVerification` and returns `204` on success. When the request includes a valid current session cookie for the same user, `verify` also marks that session's claims as email verified and removes the `email_verification` requirement. Error responses are generic and do not include emails, user ids, challenge secrets, or provider details.
|
|
383
410
|
|
|
384
|
-
|
|
411
|
+
Password HTTP handlers can start `EmailVerificationFlow` automatically when a custom auth flow returns `RequiresEmailVerification`. The HTTP response remains `requires_email_verification` and does not include the user id, email, challenge secret, or delivery provider details.
|
|
385
412
|
|
|
386
413
|
You can own the final HTTP contract while reusing core endpoints. For example, replace only password sign-up with an app-specific phone payload and keep the default sign-in endpoint:
|
|
387
414
|
|
|
@@ -489,7 +516,7 @@ export const createBrowserSession = (userId: UserId) =>
|
|
|
489
516
|
|
|
490
517
|
`SessionClaims` are a safe, transport-visible session summary. They currently include `emailVerified` and `requirements`, and are returned by password auth success, current-session, and refresh HTTP responses when present. Use `Sessions.updateClaims` to update an active session without rotating the opaque session token. Kysely storage persists claims in the existing `auth_session.metadata` column, so no additional storage migration is required.
|
|
491
518
|
|
|
492
|
-
Password auth passes the user's email verification state into `AuthFlow`, where `EmailVerificationSessionPolicy` decides
|
|
519
|
+
Password auth passes the user's email verification state into `AuthFlow`, where `EmailVerificationSessionPolicy` decides whether unverified users get a normal session or a constrained session. The default mode is `allow-unverified` for compatibility. Use `limited-session` to issue sessions with an `email_verification` requirement. Per-login email approval is a separate `UserSecurityPolicy` / `LoginApprovalPolicy` concern, not an email verification session mode.
|
|
493
520
|
|
|
494
521
|
```ts
|
|
495
522
|
import { AuthFlowLiveWithEmailVerificationPolicy } from "@effect-auth/core/AuthFlow";
|
|
@@ -503,11 +530,6 @@ export const LimitedSessionAuthFlowLive =
|
|
|
503
530
|
AuthFlowLiveWithEmailVerificationPolicy({
|
|
504
531
|
mode: "limited-session",
|
|
505
532
|
});
|
|
506
|
-
|
|
507
|
-
export const RequireBeforeSessionAuthFlowLive =
|
|
508
|
-
AuthFlowLiveWithEmailVerificationPolicy({
|
|
509
|
-
mode: "require-before-session",
|
|
510
|
-
});
|
|
511
533
|
```
|
|
512
534
|
|
|
513
535
|
For `limited-session`, protect sensitive app routes by requiring the current session to be email verified. This policy expects your route middleware to provide `CurrentSession`.
|
|
@@ -521,6 +543,113 @@ export const updateBillingSettings = Effect.gen(function* () {
|
|
|
521
543
|
}).pipe(Policy.require(Policy.requireEmailVerified()));
|
|
522
544
|
```
|
|
523
545
|
|
|
546
|
+
`LoginApprovalPolicy` is a separate primary-factor decision for high-security sign-in flows. It decides whether a verified primary factor can continue to session creation or should require app-owned login approval. When `AuthFlow` has a `LoginApprovalFlow` and `AuthFlowState`, it stores the pending primary factor, issues a `login-approval` challenge, and returns `AuthResult.RequiresLoginApproval`. The HTTP response is `requires_login_approval` and includes only `flowId`, `approvalChallengeId`, `channel`, `sessionBinding`, `sameDeviceRequired`, and `reason`.
|
|
547
|
+
|
|
548
|
+
The built-in HTTP API completes approval with `POST /auth/login-approval/approve` using `flowId`, `approvalChallengeId`, and an optional challenge `secret`. It verifies the approval, checks the approval belongs to the pending `flowId`, consumes `AuthFlowState`, marks the user email verified when the approval requested it, and creates the session. `sessionBinding: "originating-device"` protects the pending auth flow state with an HttpOnly `__Host-login-approval` cookie secret; approving from another device fails before session creation. `sessionBinding: "approval-device"` creates the session on the device that completes approval. `sessionBinding: "none"` records an approval review but does not create a user session from the approval response; the originating device finalizes later with `LoginApprovalFinalize`.
|
|
549
|
+
|
|
550
|
+
Custom policy code is the primary extension point. Presets such as `mode: "new-device"` are convenience defaults for simple apps; high-security apps should model product-specific risk logic directly in `LoginApprovalPolicy.custom`.
|
|
551
|
+
|
|
552
|
+
```ts
|
|
553
|
+
import { Effect, Layer } from "effect";
|
|
554
|
+
import { AuthFlowStateLive, AuthFlowLive } from "@effect-auth/core/AuthFlow";
|
|
555
|
+
import {
|
|
556
|
+
LoginApprovalDecision,
|
|
557
|
+
LoginApprovalDeliveryFromAuthMailerLive,
|
|
558
|
+
LoginApprovalFlowLive,
|
|
559
|
+
LoginApprovalLive,
|
|
560
|
+
LoginApprovalPolicy,
|
|
561
|
+
LoginApprovalPolicyLive,
|
|
562
|
+
} from "@effect-auth/core/LoginApproval";
|
|
563
|
+
import {
|
|
564
|
+
LoginRiskContext,
|
|
565
|
+
LoginRiskEngine,
|
|
566
|
+
LoginRiskSignal,
|
|
567
|
+
} from "@effect-auth/core/LoginRisk";
|
|
568
|
+
|
|
569
|
+
export const SimpleEmailApprovalPolicyLive = LoginApprovalPolicyLive({
|
|
570
|
+
mode: "new-device",
|
|
571
|
+
channel: "email",
|
|
572
|
+
sessionBinding: "originating-device",
|
|
573
|
+
requireForUnverifiedEmail: true,
|
|
574
|
+
skipMethods: ["passkey"],
|
|
575
|
+
skipWhenAalAtLeast: "aal2",
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
export const RiskBasedApprovalPolicyLive = Layer.succeed(
|
|
579
|
+
LoginApprovalPolicy,
|
|
580
|
+
LoginApprovalPolicy.custom((input) =>
|
|
581
|
+
Effect.succeed(
|
|
582
|
+
input.risk.hasSignal("impossible-travel")
|
|
583
|
+
? LoginApprovalDecision.emailCode({
|
|
584
|
+
reason: "impossible-travel",
|
|
585
|
+
})
|
|
586
|
+
: LoginApprovalDecision.continue()
|
|
587
|
+
)
|
|
588
|
+
)
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
export const AppLoginRiskEngineLive = Layer.succeed(
|
|
592
|
+
LoginRiskEngine,
|
|
593
|
+
LoginRiskEngine.custom((input) =>
|
|
594
|
+
Effect.succeed(
|
|
595
|
+
input.request?.country === "AU"
|
|
596
|
+
? LoginRiskContext.make({
|
|
597
|
+
level: "critical",
|
|
598
|
+
signals: [
|
|
599
|
+
LoginRiskSignal.make({
|
|
600
|
+
type: "impossible-travel",
|
|
601
|
+
level: "critical",
|
|
602
|
+
}),
|
|
603
|
+
],
|
|
604
|
+
})
|
|
605
|
+
: LoginRiskContext.make({ level: "low", signals: [] })
|
|
606
|
+
)
|
|
607
|
+
)
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
export const AppLoginApprovalLive = Layer.mergeAll(
|
|
611
|
+
AppLoginRiskEngineLive,
|
|
612
|
+
AuthFlowStateLive(),
|
|
613
|
+
LoginApprovalLive(),
|
|
614
|
+
LoginApprovalFlowLive,
|
|
615
|
+
LoginApprovalDeliveryFromAuthMailerLive,
|
|
616
|
+
AuthFlowLive
|
|
617
|
+
);
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
The default preset policy is `mode: "never"`. Passkeys or other high-assurance methods can bypass preset approval with `skipMethods` or `skipWhenAalAtLeast`. Approval `channel` and `reason` are open strings, so apps can model `admin`, `sms`, `push`, `manual-review`, or risk-specific reasons without extending core. `LoginApprovalDecision.emailCode`, `emailLink`, `sms`, `adminReview`, and `custom` set semantic `sessionBinding`; `sameDeviceRequired` remains available as the low-level legacy flag. `adminReview` uses `sessionBinding: "none"`, `LoginApprovalReview.approve` / `deny` record the out-of-band decision, and `LoginApprovalFinalize.complete` creates the session only for the originating device. Email is required only for the built-in email delivery path; non-email channels can omit it and use a custom `LoginApprovalDelivery`. `AuthFlow` always supplies policy with typed `device` and `risk`; the default risk engine returns `LoginRiskContext.make({ level: "unknown", signals: [] })`, and apps can provide `LoginRiskEngine` to calculate impossible travel, IP reputation, tenant policy, or other risk signals. `LoginRiskContext` and `LoginRiskSignal` are `Schema.Class` values, so custom engines should return `LoginRiskContext.make(...)`; policies can use helpers such as `input.risk.hasSignal(...)`, `input.risk.getSignal(...)`, and `input.risk.isAtLeast(...)`. `LoginRiskEngine` receives request context only when the primary-factor integration supplies it; core does not trust proxy or geo headers by default.
|
|
621
|
+
|
|
622
|
+
`LoginApproval.inspect` validates a login approval challenge without consuming it. Use it for GET preview pages or admin panels that need to display pending approval details. For admin, push, or Slack approvals, configure `LoginApprovalReview` with a persistent store, let the out-of-band actor call `approve` or `deny`, then have the user's original browser poll review status and call `LoginApprovalFinalize.complete` with its pending-cookie secret. The built-in HTTP API exposes this originating-browser path as `POST /auth/login-approval/status` and `POST /auth/login-approval/finalize`, also available as `client.loginApproval.status(...)` and `client.loginApproval.finalize(...)`. `KyselyAuthStorageLive` and `EffectQbSqliteAuthStorageLive` provide durable `LoginApprovalReviewStore`; the included `LoginApprovalReviewStoreMemoryLive` is for tests and local composition.
|
|
623
|
+
|
|
624
|
+
`AuthFlowState` stores a short-lived, single-use pending primary-factor result in the existing challenge store. It is the bridge between “password/passkey/OTP verified” and “approval/MFA completed, now create a session”. The stored state contains `userId`, `method`, `amr`, `aal`, optional session claims, and app metadata such as `redirectTo`.
|
|
625
|
+
|
|
626
|
+
```ts
|
|
627
|
+
import { AuthFlowStateLive } from "@effect-auth/core/AuthFlow";
|
|
628
|
+
|
|
629
|
+
export const AppAuthFlowStateLive = AuthFlowStateLive();
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
## Trusted Devices
|
|
633
|
+
|
|
634
|
+
`TrustedDevice` is a small boundary for “remember this device” login policy. It stores only an HMAC hash of an opaque device token and returns `LoginDeviceContext` as `unknown`, `new`, or `known`. `LoginApprovalPolicy({ mode: "new-device" })` can use that context to require approval only for first-time devices.
|
|
635
|
+
|
|
636
|
+
```ts
|
|
637
|
+
import { Layer, Redacted } from "effect";
|
|
638
|
+
import {
|
|
639
|
+
TrustedDeviceCookieLive,
|
|
640
|
+
TrustedDeviceLive,
|
|
641
|
+
TrustedDeviceStoreMemoryLive,
|
|
642
|
+
} from "@effect-auth/core/TrustedDevice";
|
|
643
|
+
|
|
644
|
+
export const AppTrustedDeviceLive = Layer.mergeAll(
|
|
645
|
+
TrustedDeviceStoreMemoryLive,
|
|
646
|
+
TrustedDeviceLive({ secret: Redacted.make("trusted-device-secret") }),
|
|
647
|
+
TrustedDeviceCookieLive()
|
|
648
|
+
);
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
The built-in password sign-in, email OTP verify, and magic-link verify HTTP handlers read the HttpOnly `__Host-auth-device` cookie when `TrustedDeviceCookie` is provided and pass the trusted-device token into `AuthFlow`; `AuthFlow` resolves the device status centrally with `TrustedDevice`. `POST /auth/login-approval/approve` accepts `rememberDevice: true`; when `TrustedDevice` and `TrustedDeviceCookie` are provided it trusts the approved user/device and sets `__Host-auth-device` with the final session response. `KyselyAuthStorageLive` and `EffectQbSqliteAuthStorageLive` provide durable `TrustedDeviceStore`; the in-memory store is intended for tests and local composition.
|
|
652
|
+
|
|
524
653
|
## Challenge
|
|
525
654
|
|
|
526
655
|
`Challenge` is the shared primitive for short-lived proofs: email OTP, magic link, passkey challenges, OAuth state, reset password, and MFA.
|
|
@@ -640,8 +769,14 @@ The first storage adapter is Kysely-based. Applications own their Kysely instanc
|
|
|
640
769
|
```ts
|
|
641
770
|
import { makeKyselyStorage } from "@effect-auth/core/KyselyStorage";
|
|
642
771
|
|
|
643
|
-
const {
|
|
644
|
-
|
|
772
|
+
const {
|
|
773
|
+
userStore,
|
|
774
|
+
credentialStore,
|
|
775
|
+
sessionStore,
|
|
776
|
+
verificationStore,
|
|
777
|
+
loginApprovalReviewStore,
|
|
778
|
+
trustedDeviceStore,
|
|
779
|
+
} = makeKyselyStorage(db);
|
|
645
780
|
```
|
|
646
781
|
|
|
647
782
|
For Cloudflare D1, use the first-party Kysely dialect instead of an external D1 dialect package:
|
|
@@ -661,7 +796,7 @@ const db = makeD1Kysely<DB>(env.DB);
|
|
|
661
796
|
const StorageLive = KyselyAuthStorageLive(db);
|
|
662
797
|
```
|
|
663
798
|
|
|
664
|
-
The default schema expects `auth_user`, `auth_credential`, `auth_session`, and `
|
|
799
|
+
The default schema expects `auth_user`, `auth_credential`, `auth_session`, `auth_verification`, `auth_login_approval_review`, `auth_trusted_device`, and `auth_passkey_credential` tables in the Kysely database type:
|
|
665
800
|
|
|
666
801
|
```ts
|
|
667
802
|
import type { KyselyAuthDatabase } from "@effect-auth/core/KyselyStorage";
|
|
@@ -747,7 +882,7 @@ const authTables = defineEffectQbAuthTables({
|
|
|
747
882
|
}) satisfies Query.MutationInputOf<Table.UpdateOf<typeof users>>,
|
|
748
883
|
},
|
|
749
884
|
|
|
750
|
-
// credential/session/verification mappings follow the same pattern.
|
|
885
|
+
// credential/session/verification/review/trusted-device mappings follow the same pattern.
|
|
751
886
|
});
|
|
752
887
|
|
|
753
888
|
const StorageLive = EffectQbSqliteAuthStorageLive(executor, {
|
package/dist/ApiKey.d.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { Brand, Context, Effect, Layer, Redacted } from "effect";
|
|
2
|
+
import type * as OptionTypes from "effect/Option";
|
|
3
|
+
import { Crypto } from "./Crypto.js";
|
|
4
|
+
import type { CryptoService } from "./Crypto.js";
|
|
5
|
+
import type { UnixMillis, UserId } from "./Identifiers.js";
|
|
6
|
+
export type ApiKeyId = Brand.Branded<string, "auth/ApiKeyId">;
|
|
7
|
+
export declare const ApiKeyId: Brand.Constructor<ApiKeyId>;
|
|
8
|
+
export type ApiKeyPrefix = Brand.Branded<string, "auth/ApiKeyPrefix">;
|
|
9
|
+
export declare const ApiKeyPrefix: Brand.Constructor<ApiKeyPrefix>;
|
|
10
|
+
export type ApiKeySecret = Brand.Branded<string, "auth/ApiKeySecret">;
|
|
11
|
+
export declare const ApiKeySecret: Brand.Constructor<ApiKeySecret>;
|
|
12
|
+
export type ApiKeySecretHash = Brand.Branded<string, "auth/ApiKeySecretHash">;
|
|
13
|
+
export declare const ApiKeySecretHash: Brand.Constructor<ApiKeySecretHash>;
|
|
14
|
+
export declare const defaultApiKeyPrefixBytes = 8;
|
|
15
|
+
export declare const defaultApiKeySecretBytes = 32;
|
|
16
|
+
export declare const defaultApiKeyIdBytes = 16;
|
|
17
|
+
export type ApiKeyOperation = "generate" | "parse" | "hash" | "verify";
|
|
18
|
+
declare const ApiKeyError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
19
|
+
readonly _tag: "ApiKeyError";
|
|
20
|
+
} & Readonly<A>;
|
|
21
|
+
export declare class ApiKeyError extends ApiKeyError_base<{
|
|
22
|
+
readonly operation: ApiKeyOperation;
|
|
23
|
+
readonly message: string;
|
|
24
|
+
readonly cause?: unknown;
|
|
25
|
+
}> {
|
|
26
|
+
}
|
|
27
|
+
export interface ApiKeyGenerateInput {
|
|
28
|
+
readonly prefixBytes?: number;
|
|
29
|
+
readonly secretBytes?: number;
|
|
30
|
+
}
|
|
31
|
+
export interface GeneratedApiKeySecret {
|
|
32
|
+
readonly prefix: ApiKeyPrefix;
|
|
33
|
+
readonly secret: Redacted.Redacted<ApiKeySecret>;
|
|
34
|
+
}
|
|
35
|
+
export interface ApiKeyHashInput {
|
|
36
|
+
readonly secret: Redacted.Redacted<string>;
|
|
37
|
+
}
|
|
38
|
+
export interface ApiKeyVerifyInput extends ApiKeyHashInput {
|
|
39
|
+
readonly hash: ApiKeySecretHash;
|
|
40
|
+
}
|
|
41
|
+
export interface ApiKeysService {
|
|
42
|
+
readonly generate: (input?: ApiKeyGenerateInput) => Effect.Effect<GeneratedApiKeySecret, ApiKeyError>;
|
|
43
|
+
readonly parse: (secret: Redacted.Redacted<string>) => Effect.Effect<ApiKeyPrefix, ApiKeyError>;
|
|
44
|
+
readonly hash: (input: ApiKeyHashInput) => Effect.Effect<ApiKeySecretHash, ApiKeyError>;
|
|
45
|
+
readonly verify: (input: ApiKeyVerifyInput) => Effect.Effect<boolean, ApiKeyError>;
|
|
46
|
+
}
|
|
47
|
+
declare const ApiKeys_base: Context.ServiceClass<ApiKeys, "auth/ApiKeys", ApiKeysService>;
|
|
48
|
+
export declare class ApiKeys extends ApiKeys_base {
|
|
49
|
+
}
|
|
50
|
+
export declare namespace ApiKeys {
|
|
51
|
+
const make: (service: ApiKeysService) => ApiKeysService;
|
|
52
|
+
}
|
|
53
|
+
export interface ApiKeysDependencies {
|
|
54
|
+
readonly crypto: CryptoService;
|
|
55
|
+
}
|
|
56
|
+
export declare const makeApiKeys: (dependencies: ApiKeysDependencies) => ApiKeysService;
|
|
57
|
+
export declare const ApiKeysLive: Layer.Layer<ApiKeys, never, Crypto>;
|
|
58
|
+
export interface ApiKeyRecord {
|
|
59
|
+
readonly id: ApiKeyId;
|
|
60
|
+
readonly userId: UserId;
|
|
61
|
+
readonly prefix: ApiKeyPrefix;
|
|
62
|
+
readonly secretHash: ApiKeySecretHash;
|
|
63
|
+
readonly scopes: readonly string[];
|
|
64
|
+
readonly createdAt: UnixMillis;
|
|
65
|
+
readonly expiresAt?: UnixMillis;
|
|
66
|
+
readonly lastUsedAt?: UnixMillis;
|
|
67
|
+
readonly revokedAt?: UnixMillis;
|
|
68
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
69
|
+
}
|
|
70
|
+
export interface ApiKeyListByUserInput {
|
|
71
|
+
readonly userId: UserId;
|
|
72
|
+
readonly includeRevoked?: boolean;
|
|
73
|
+
}
|
|
74
|
+
export interface ApiKeyMarkUsedInput {
|
|
75
|
+
readonly id: ApiKeyId;
|
|
76
|
+
readonly lastUsedAt: UnixMillis;
|
|
77
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
78
|
+
}
|
|
79
|
+
export interface ApiKeyRevokeInput {
|
|
80
|
+
readonly id: ApiKeyId;
|
|
81
|
+
readonly revokedAt: UnixMillis;
|
|
82
|
+
readonly reason?: string;
|
|
83
|
+
}
|
|
84
|
+
export type ApiKeyStoreOperation = "insert" | "find" | "list" | "mark-used" | "revoke";
|
|
85
|
+
declare const ApiKeyStoreError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
86
|
+
readonly _tag: "ApiKeyStoreError";
|
|
87
|
+
} & Readonly<A>;
|
|
88
|
+
export declare class ApiKeyStoreError extends ApiKeyStoreError_base<{
|
|
89
|
+
readonly operation: ApiKeyStoreOperation;
|
|
90
|
+
readonly message: string;
|
|
91
|
+
readonly cause?: unknown;
|
|
92
|
+
}> {
|
|
93
|
+
}
|
|
94
|
+
export interface ApiKeyStoreService {
|
|
95
|
+
readonly insert: (row: ApiKeyRecord) => Effect.Effect<void, ApiKeyStoreError>;
|
|
96
|
+
readonly findById: (id: ApiKeyId) => Effect.Effect<OptionTypes.Option<ApiKeyRecord>, ApiKeyStoreError>;
|
|
97
|
+
readonly findByPrefix: (prefix: ApiKeyPrefix) => Effect.Effect<OptionTypes.Option<ApiKeyRecord>, ApiKeyStoreError>;
|
|
98
|
+
readonly listByUser: (input: ApiKeyListByUserInput) => Effect.Effect<readonly ApiKeyRecord[], ApiKeyStoreError>;
|
|
99
|
+
readonly markUsed: (input: ApiKeyMarkUsedInput) => Effect.Effect<OptionTypes.Option<ApiKeyRecord>, ApiKeyStoreError>;
|
|
100
|
+
readonly revoke: (input: ApiKeyRevokeInput) => Effect.Effect<void, ApiKeyStoreError>;
|
|
101
|
+
}
|
|
102
|
+
declare const ApiKeyStore_base: Context.ServiceClass<ApiKeyStore, "auth/ApiKeyStore", ApiKeyStoreService>;
|
|
103
|
+
export declare class ApiKeyStore extends ApiKeyStore_base {
|
|
104
|
+
}
|
|
105
|
+
export declare namespace ApiKeyStore {
|
|
106
|
+
const make: (service: ApiKeyStoreService) => ApiKeyStoreService;
|
|
107
|
+
}
|
|
108
|
+
export declare const makeApiKeyStoreMemory: () => ApiKeyStoreService;
|
|
109
|
+
export declare const ApiKeyStoreMemoryLive: Layer.Layer<ApiKeyStore, never, never>;
|
|
110
|
+
export interface ApiKeyInfo {
|
|
111
|
+
readonly id: ApiKeyId;
|
|
112
|
+
readonly userId: UserId;
|
|
113
|
+
readonly prefix: ApiKeyPrefix;
|
|
114
|
+
readonly scopes: readonly string[];
|
|
115
|
+
readonly createdAt: UnixMillis;
|
|
116
|
+
readonly expiresAt?: UnixMillis;
|
|
117
|
+
readonly lastUsedAt?: UnixMillis;
|
|
118
|
+
readonly revokedAt?: UnixMillis;
|
|
119
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
120
|
+
}
|
|
121
|
+
export interface ApiKeyCreateForUserInput {
|
|
122
|
+
readonly userId: UserId;
|
|
123
|
+
readonly keyId?: ApiKeyId;
|
|
124
|
+
readonly scopes?: readonly string[];
|
|
125
|
+
readonly now?: UnixMillis;
|
|
126
|
+
readonly expiresAt?: UnixMillis;
|
|
127
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
128
|
+
}
|
|
129
|
+
export interface CreatedApiKey {
|
|
130
|
+
readonly secret: Redacted.Redacted<ApiKeySecret>;
|
|
131
|
+
readonly key: ApiKeyInfo;
|
|
132
|
+
}
|
|
133
|
+
export interface ApiKeyManagementListInput {
|
|
134
|
+
readonly userId: UserId;
|
|
135
|
+
readonly includeRevoked?: boolean;
|
|
136
|
+
}
|
|
137
|
+
export interface ApiKeyManagementRevokeInput {
|
|
138
|
+
readonly userId: UserId;
|
|
139
|
+
readonly keyId: ApiKeyId;
|
|
140
|
+
readonly revokedAt?: UnixMillis;
|
|
141
|
+
readonly reason?: string;
|
|
142
|
+
}
|
|
143
|
+
export type ApiKeyManagementOperation = "create" | "list" | "revoke";
|
|
144
|
+
declare const ApiKeyManagementError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
145
|
+
readonly _tag: "ApiKeyManagementError";
|
|
146
|
+
} & Readonly<A>;
|
|
147
|
+
export declare class ApiKeyManagementError extends ApiKeyManagementError_base<{
|
|
148
|
+
readonly operation: ApiKeyManagementOperation;
|
|
149
|
+
readonly message: string;
|
|
150
|
+
readonly cause?: unknown;
|
|
151
|
+
}> {
|
|
152
|
+
}
|
|
153
|
+
export interface ApiKeyManagementService {
|
|
154
|
+
readonly createForUser: (input: ApiKeyCreateForUserInput) => Effect.Effect<CreatedApiKey, ApiKeyManagementError>;
|
|
155
|
+
readonly listForUser: (input: ApiKeyManagementListInput) => Effect.Effect<readonly ApiKeyInfo[], ApiKeyManagementError>;
|
|
156
|
+
readonly revokeForUser: (input: ApiKeyManagementRevokeInput) => Effect.Effect<void, ApiKeyManagementError>;
|
|
157
|
+
}
|
|
158
|
+
declare const ApiKeyManagement_base: Context.ServiceClass<ApiKeyManagement, "auth/ApiKeyManagement", ApiKeyManagementService>;
|
|
159
|
+
export declare class ApiKeyManagement extends ApiKeyManagement_base {
|
|
160
|
+
}
|
|
161
|
+
export declare namespace ApiKeyManagement {
|
|
162
|
+
const make: (service: ApiKeyManagementService) => ApiKeyManagementService;
|
|
163
|
+
}
|
|
164
|
+
export interface ApiKeyManagementDependencies {
|
|
165
|
+
readonly apiKeys: ApiKeysService;
|
|
166
|
+
readonly crypto: CryptoService;
|
|
167
|
+
readonly keyStore: ApiKeyStoreService;
|
|
168
|
+
readonly keyIdBytes?: number;
|
|
169
|
+
}
|
|
170
|
+
export declare const makeApiKeyManagement: (dependencies: ApiKeyManagementDependencies) => ApiKeyManagementService;
|
|
171
|
+
export declare const ApiKeyManagementLive: Layer.Layer<ApiKeyManagement, never, Crypto | ApiKeys | ApiKeyStore>;
|
|
172
|
+
export interface ApiKeyVerificationInput {
|
|
173
|
+
readonly secret: Redacted.Redacted<string>;
|
|
174
|
+
readonly now?: UnixMillis;
|
|
175
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
176
|
+
}
|
|
177
|
+
export interface ApiKeyVerificationResult {
|
|
178
|
+
readonly valid: boolean;
|
|
179
|
+
readonly key?: ApiKeyInfo;
|
|
180
|
+
}
|
|
181
|
+
export type ApiKeyVerificationOperation = "verify";
|
|
182
|
+
declare const ApiKeyVerificationError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
183
|
+
readonly _tag: "ApiKeyVerificationError";
|
|
184
|
+
} & Readonly<A>;
|
|
185
|
+
export declare class ApiKeyVerificationError extends ApiKeyVerificationError_base<{
|
|
186
|
+
readonly operation: ApiKeyVerificationOperation;
|
|
187
|
+
readonly message: string;
|
|
188
|
+
readonly cause?: unknown;
|
|
189
|
+
}> {
|
|
190
|
+
}
|
|
191
|
+
export interface ApiKeyVerificationService {
|
|
192
|
+
readonly verify: (input: ApiKeyVerificationInput) => Effect.Effect<ApiKeyVerificationResult, ApiKeyVerificationError>;
|
|
193
|
+
}
|
|
194
|
+
declare const ApiKeyVerification_base: Context.ServiceClass<ApiKeyVerification, "auth/ApiKeyVerification", ApiKeyVerificationService>;
|
|
195
|
+
export declare class ApiKeyVerification extends ApiKeyVerification_base {
|
|
196
|
+
}
|
|
197
|
+
export declare namespace ApiKeyVerification {
|
|
198
|
+
const make: (service: ApiKeyVerificationService) => ApiKeyVerificationService;
|
|
199
|
+
}
|
|
200
|
+
export interface ApiKeyVerificationDependencies {
|
|
201
|
+
readonly apiKeys: ApiKeysService;
|
|
202
|
+
readonly keyStore: ApiKeyStoreService;
|
|
203
|
+
}
|
|
204
|
+
export declare const makeApiKeyVerification: (dependencies: ApiKeyVerificationDependencies) => ApiKeyVerificationService;
|
|
205
|
+
export declare const ApiKeyVerificationLive: Layer.Layer<ApiKeyVerification, never, ApiKeys | ApiKeyStore>;
|
|
206
|
+
export {};
|
|
207
|
+
//# sourceMappingURL=ApiKey.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApiKey.d.ts","sourceRoot":"","sources":["../src/ApiKey.ts"],"names":[],"mappings":"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"}
|