@effect-auth/core 0.1.0-alpha.16 → 0.1.0-alpha.18
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/LICENSE +21 -0
- package/README.md +308 -133
- package/dist/AbuseProtection.d.ts +100 -0
- package/dist/AbuseProtection.d.ts.map +1 -0
- package/dist/AbuseProtection.js +71 -0
- package/dist/AbuseProtection.js.map +1 -0
- package/dist/AccessGrant.d.ts +344 -0
- package/dist/AccessGrant.d.ts.map +1 -0
- package/dist/AccessGrant.js +687 -0
- package/dist/AccessGrant.js.map +1 -0
- package/dist/ApiKey.d.ts +0 -12
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +36 -27
- package/dist/ApiKey.js.map +1 -1
- package/dist/Assurance.d.ts +415 -0
- package/dist/Assurance.d.ts.map +1 -0
- package/dist/Assurance.js +764 -0
- package/dist/Assurance.js.map +1 -0
- package/dist/AtomicPlanExecutor.d.ts +28 -0
- package/dist/AtomicPlanExecutor.d.ts.map +1 -0
- package/dist/AtomicPlanExecutor.js +5 -0
- package/dist/AtomicPlanExecutor.js.map +1 -0
- package/dist/AuditLog.d.ts +2314 -52
- package/dist/AuditLog.d.ts.map +1 -1
- package/dist/AuditLog.js +511 -33
- package/dist/AuditLog.js.map +1 -1
- package/dist/AuthFlow.d.ts +128 -38
- package/dist/AuthFlow.d.ts.map +1 -1
- package/dist/AuthFlow.js +611 -263
- package/dist/AuthFlow.js.map +1 -1
- package/dist/AuthRateLimit.d.ts +96 -0
- package/dist/AuthRateLimit.d.ts.map +1 -0
- package/dist/AuthRateLimit.js +212 -0
- package/dist/AuthRateLimit.js.map +1 -0
- package/dist/Challenge.d.ts +0 -3
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js +1 -4
- package/dist/Challenge.js.map +1 -1
- package/dist/Client.d.ts +205 -10
- package/dist/Client.d.ts.map +1 -1
- package/dist/Client.js +397 -16
- package/dist/Client.js.map +1 -1
- package/dist/CloudflareTurnstile.d.ts +28 -0
- package/dist/CloudflareTurnstile.d.ts.map +1 -0
- package/dist/CloudflareTurnstile.js +205 -0
- package/dist/CloudflareTurnstile.js.map +1 -0
- package/dist/Crypto.d.ts +0 -3
- package/dist/Crypto.d.ts.map +1 -1
- package/dist/Crypto.js +1 -4
- package/dist/Crypto.js.map +1 -1
- package/dist/DelegatedAccessStorageMigrations.d.ts +7 -0
- package/dist/DelegatedAccessStorageMigrations.d.ts.map +1 -0
- package/dist/DelegatedAccessStorageMigrations.js +159 -0
- package/dist/DelegatedAccessStorageMigrations.js.map +1 -0
- package/dist/DisposableEmailDomains.d.ts +19 -0
- package/dist/DisposableEmailDomains.d.ts.map +1 -0
- package/dist/DisposableEmailDomains.js +85 -0
- package/dist/DisposableEmailDomains.js.map +1 -0
- package/dist/DomainVerification.d.ts +0 -9
- package/dist/DomainVerification.d.ts.map +1 -1
- package/dist/DomainVerification.js +36 -22
- package/dist/DomainVerification.js.map +1 -1
- package/dist/DrizzleBunSqliteStorage.d.ts +18 -0
- package/dist/DrizzleBunSqliteStorage.d.ts.map +1 -0
- package/dist/DrizzleBunSqliteStorage.js +11 -0
- package/dist/DrizzleBunSqliteStorage.js.map +1 -0
- package/dist/DrizzleD1SqliteStorage.d.ts +4 -4
- package/dist/DrizzleD1SqliteStorage.d.ts.map +1 -1
- package/dist/DrizzleD1SqliteStorage.js +5 -5
- package/dist/DrizzleD1SqliteStorage.js.map +1 -1
- package/dist/DrizzleDurableObjectSqliteStorage.d.ts +19 -0
- package/dist/DrizzleDurableObjectSqliteStorage.d.ts.map +1 -0
- package/dist/DrizzleDurableObjectSqliteStorage.js +14 -0
- package/dist/DrizzleDurableObjectSqliteStorage.js.map +1 -0
- package/dist/DrizzleEffectSqliteStorage.d.ts +13 -4
- package/dist/DrizzleEffectSqliteStorage.d.ts.map +1 -1
- package/dist/DrizzleEffectSqliteStorage.js +71 -4
- package/dist/DrizzleEffectSqliteStorage.js.map +1 -1
- package/dist/DrizzleNodeSqliteStorage.d.ts +18 -0
- package/dist/DrizzleNodeSqliteStorage.d.ts.map +1 -0
- package/dist/DrizzleNodeSqliteStorage.js +11 -0
- package/dist/DrizzleNodeSqliteStorage.js.map +1 -0
- package/dist/DrizzleWasmSqliteStorage.d.ts +23 -0
- package/dist/DrizzleWasmSqliteStorage.d.ts.map +1 -0
- package/dist/DrizzleWasmSqliteStorage.js +15 -0
- package/dist/DrizzleWasmSqliteStorage.js.map +1 -0
- package/dist/EffectQbPostgresDelegatedAccessStorage.d.ts +34 -0
- package/dist/EffectQbPostgresDelegatedAccessStorage.d.ts.map +1 -0
- package/dist/EffectQbPostgresDelegatedAccessStorage.js +499 -0
- package/dist/EffectQbPostgresDelegatedAccessStorage.js.map +1 -0
- package/dist/EffectQbPostgresStorage.d.ts +171 -0
- package/dist/EffectQbPostgresStorage.d.ts.map +1 -0
- package/dist/EffectQbPostgresStorage.js +4280 -0
- package/dist/EffectQbPostgresStorage.js.map +1 -0
- package/dist/EffectQbSqliteDelegatedAccessStorage.d.ts +112 -0
- package/dist/EffectQbSqliteDelegatedAccessStorage.d.ts.map +1 -0
- package/dist/EffectQbSqliteDelegatedAccessStorage.js +602 -0
- package/dist/EffectQbSqliteDelegatedAccessStorage.js.map +1 -0
- package/dist/EffectQbSqliteStorage.d.ts +181 -23
- package/dist/EffectQbSqliteStorage.d.ts.map +1 -1
- package/dist/EffectQbSqliteStorage.js +1728 -135
- package/dist/EffectQbSqliteStorage.js.map +1 -1
- package/dist/EmailAuth.d.ts +65 -0
- package/dist/EmailAuth.d.ts.map +1 -0
- package/dist/EmailAuth.js +171 -0
- package/dist/EmailAuth.js.map +1 -0
- package/dist/EmailIdentityAuth.d.ts +30 -0
- package/dist/EmailIdentityAuth.d.ts.map +1 -0
- package/dist/EmailIdentityAuth.js +142 -0
- package/dist/EmailIdentityAuth.js.map +1 -0
- package/dist/EmailOtp.d.ts +10 -11
- package/dist/EmailOtp.d.ts.map +1 -1
- package/dist/EmailOtp.js +44 -81
- package/dist/EmailOtp.js.map +1 -1
- package/dist/EmailRisk.d.ts +48 -0
- package/dist/EmailRisk.d.ts.map +1 -0
- package/dist/EmailRisk.js +33 -0
- package/dist/EmailRisk.js.map +1 -0
- package/dist/EmailVerification.d.ts +10 -16
- package/dist/EmailVerification.d.ts.map +1 -1
- package/dist/EmailVerification.js +85 -42
- package/dist/EmailVerification.js.map +1 -1
- package/dist/Guard.d.ts +11 -0
- package/dist/Guard.d.ts.map +1 -0
- package/dist/Guard.js +11 -0
- package/dist/Guard.js.map +1 -0
- package/dist/HibpPwnedPasswords.d.ts +56 -0
- package/dist/HibpPwnedPasswords.d.ts.map +1 -0
- package/dist/HibpPwnedPasswords.js +232 -0
- package/dist/HibpPwnedPasswords.js.map +1 -0
- package/dist/HttpApi/AdminPermissionDefinition.d.ts +4 -0
- package/dist/HttpApi/AdminPermissionDefinition.d.ts.map +1 -0
- package/dist/HttpApi/AdminPermissionDefinition.js +4 -0
- package/dist/HttpApi/AdminPermissionDefinition.js.map +1 -0
- package/dist/HttpApi/AdminSecurityTimeline.d.ts +4 -0
- package/dist/HttpApi/AdminSecurityTimeline.d.ts.map +1 -0
- package/dist/HttpApi/AdminSecurityTimeline.js +4 -0
- package/dist/HttpApi/AdminSecurityTimeline.js.map +1 -0
- package/dist/HttpApi/AdminSession.d.ts +4 -0
- package/dist/HttpApi/AdminSession.d.ts.map +1 -0
- package/dist/HttpApi/AdminSession.js +4 -0
- package/dist/HttpApi/AdminSession.js.map +1 -0
- package/dist/HttpApi/AdminTrustedDevice.d.ts +4 -0
- package/dist/HttpApi/AdminTrustedDevice.d.ts.map +1 -0
- package/dist/HttpApi/AdminTrustedDevice.js +4 -0
- package/dist/HttpApi/AdminTrustedDevice.js.map +1 -0
- package/dist/HttpApi/Api.d.ts +879 -877
- package/dist/HttpApi/Api.d.ts.map +1 -1
- package/dist/HttpApi/Api.js +2160 -896
- package/dist/HttpApi/Api.js.map +1 -1
- package/dist/HttpApi/ApiKey.d.ts +4 -0
- package/dist/HttpApi/ApiKey.d.ts.map +1 -0
- package/dist/HttpApi/ApiKey.js +4 -0
- package/dist/HttpApi/ApiKey.js.map +1 -0
- package/dist/HttpApi/Audit.d.ts +6 -0
- package/dist/HttpApi/Audit.d.ts.map +1 -0
- package/dist/HttpApi/Audit.js +10 -0
- package/dist/HttpApi/Audit.js.map +1 -0
- package/dist/HttpApi/ClientApi.d.ts +917 -0
- package/dist/HttpApi/ClientApi.d.ts.map +1 -0
- package/dist/HttpApi/ClientApi.js +101 -0
- package/dist/HttpApi/ClientApi.js.map +1 -0
- package/dist/HttpApi/EmailAuth.d.ts +6 -0
- package/dist/HttpApi/EmailAuth.d.ts.map +1 -0
- package/dist/HttpApi/EmailAuth.js +6 -0
- package/dist/HttpApi/EmailAuth.js.map +1 -0
- package/dist/HttpApi/EmailGuards.d.ts +115 -0
- package/dist/HttpApi/EmailGuards.d.ts.map +1 -0
- package/dist/HttpApi/EmailGuards.js +191 -0
- package/dist/HttpApi/EmailGuards.js.map +1 -0
- package/dist/HttpApi/EmailOtp.d.ts +6 -0
- package/dist/HttpApi/EmailOtp.d.ts.map +1 -0
- package/dist/HttpApi/EmailOtp.js +6 -0
- package/dist/HttpApi/EmailOtp.js.map +1 -0
- package/dist/HttpApi/EmailVerification.d.ts +6 -0
- package/dist/HttpApi/EmailVerification.d.ts.map +1 -0
- package/dist/HttpApi/EmailVerification.js +6 -0
- package/dist/HttpApi/EmailVerification.js.map +1 -0
- package/dist/HttpApi/Endpoints.d.ts +629 -301
- package/dist/HttpApi/Endpoints.d.ts.map +1 -1
- package/dist/HttpApi/Endpoints.js +178 -120
- package/dist/HttpApi/Endpoints.js.map +1 -1
- package/dist/HttpApi/Errors.d.ts +26 -7
- package/dist/HttpApi/Errors.d.ts.map +1 -1
- package/dist/HttpApi/Errors.js +26 -4
- package/dist/HttpApi/Errors.js.map +1 -1
- package/dist/HttpApi/Identity.d.ts +6 -0
- package/dist/HttpApi/Identity.d.ts.map +1 -0
- package/dist/HttpApi/Identity.js +6 -0
- package/dist/HttpApi/Identity.js.map +1 -0
- package/dist/HttpApi/IdentityGuards.d.ts +31 -0
- package/dist/HttpApi/IdentityGuards.d.ts.map +1 -0
- package/dist/HttpApi/IdentityGuards.js +33 -0
- package/dist/HttpApi/IdentityGuards.js.map +1 -0
- package/dist/HttpApi/Jwt.d.ts +4 -0
- package/dist/HttpApi/Jwt.d.ts.map +1 -0
- package/dist/HttpApi/Jwt.js +4 -0
- package/dist/HttpApi/Jwt.js.map +1 -0
- package/dist/HttpApi/JwtDiscovery.d.ts +4 -0
- package/dist/HttpApi/JwtDiscovery.d.ts.map +1 -0
- package/dist/HttpApi/JwtDiscovery.js +4 -0
- package/dist/HttpApi/JwtDiscovery.js.map +1 -0
- package/dist/HttpApi/LoginApproval.d.ts +5 -0
- package/dist/HttpApi/LoginApproval.d.ts.map +1 -0
- package/dist/HttpApi/LoginApproval.js +5 -0
- package/dist/HttpApi/LoginApproval.js.map +1 -0
- package/dist/HttpApi/LoginNotification.d.ts +5 -0
- package/dist/HttpApi/LoginNotification.d.ts.map +1 -0
- package/dist/HttpApi/LoginNotification.js +5 -0
- package/dist/HttpApi/LoginNotification.js.map +1 -0
- package/dist/HttpApi/MagicLink.d.ts +6 -0
- package/dist/HttpApi/MagicLink.d.ts.map +1 -0
- package/dist/HttpApi/MagicLink.js +6 -0
- package/dist/HttpApi/MagicLink.js.map +1 -0
- package/dist/HttpApi/Mfa.d.ts +5 -0
- package/dist/HttpApi/Mfa.d.ts.map +1 -0
- package/dist/HttpApi/Mfa.js +5 -0
- package/dist/HttpApi/Mfa.js.map +1 -0
- package/dist/HttpApi/Middleware.d.ts +14 -0
- package/dist/HttpApi/Middleware.d.ts.map +1 -0
- package/dist/HttpApi/Middleware.js +5 -0
- package/dist/HttpApi/Middleware.js.map +1 -0
- package/dist/HttpApi/OAuth.d.ts +4 -0
- package/dist/HttpApi/OAuth.d.ts.map +1 -0
- package/dist/HttpApi/OAuth.js +4 -0
- package/dist/HttpApi/OAuth.js.map +1 -0
- package/dist/HttpApi/OAuthProviderAuthorization.d.ts +4 -0
- package/dist/HttpApi/OAuthProviderAuthorization.d.ts.map +1 -0
- package/dist/HttpApi/OAuthProviderAuthorization.js +4 -0
- package/dist/HttpApi/OAuthProviderAuthorization.js.map +1 -0
- package/dist/HttpApi/OAuthToken.d.ts +4 -0
- package/dist/HttpApi/OAuthToken.d.ts.map +1 -0
- package/dist/HttpApi/OAuthToken.js +4 -0
- package/dist/HttpApi/OAuthToken.js.map +1 -0
- package/dist/HttpApi/OidcDiscovery.d.ts +4 -0
- package/dist/HttpApi/OidcDiscovery.d.ts.map +1 -0
- package/dist/HttpApi/OidcDiscovery.js +4 -0
- package/dist/HttpApi/OidcDiscovery.js.map +1 -0
- package/dist/HttpApi/Operations/AdminPermissionDefinition.d.ts +28 -0
- package/dist/HttpApi/Operations/AdminPermissionDefinition.d.ts.map +1 -0
- package/dist/HttpApi/Operations/AdminPermissionDefinition.js +5 -0
- package/dist/HttpApi/Operations/AdminPermissionDefinition.js.map +1 -0
- package/dist/HttpApi/Operations/AdminSession.d.ts +16 -0
- package/dist/HttpApi/Operations/AdminSession.d.ts.map +1 -0
- package/dist/HttpApi/Operations/AdminSession.js +5 -0
- package/dist/HttpApi/Operations/AdminSession.js.map +1 -0
- package/dist/HttpApi/Operations/ApiKey.d.ts +16 -0
- package/dist/HttpApi/Operations/ApiKey.d.ts.map +1 -0
- package/dist/HttpApi/Operations/ApiKey.js +5 -0
- package/dist/HttpApi/Operations/ApiKey.js.map +1 -0
- package/dist/HttpApi/Operations/Discovery.d.ts +19 -0
- package/dist/HttpApi/Operations/Discovery.d.ts.map +1 -0
- package/dist/HttpApi/Operations/Discovery.js +7 -0
- package/dist/HttpApi/Operations/Discovery.js.map +1 -0
- package/dist/HttpApi/Operations/EmailAuth.d.ts +12 -0
- package/dist/HttpApi/Operations/EmailAuth.d.ts.map +1 -0
- package/dist/HttpApi/Operations/EmailAuth.js +5 -0
- package/dist/HttpApi/Operations/EmailAuth.js.map +1 -0
- package/dist/HttpApi/Operations/EmailOtp.d.ts +14 -0
- package/dist/HttpApi/Operations/EmailOtp.d.ts.map +1 -0
- package/dist/HttpApi/Operations/EmailOtp.js +5 -0
- package/dist/HttpApi/Operations/EmailOtp.js.map +1 -0
- package/dist/HttpApi/Operations/EmailVerification.d.ts +14 -0
- package/dist/HttpApi/Operations/EmailVerification.d.ts.map +1 -0
- package/dist/HttpApi/Operations/EmailVerification.js +5 -0
- package/dist/HttpApi/Operations/EmailVerification.js.map +1 -0
- package/dist/HttpApi/Operations/HttpOperation.d.ts +4 -0
- package/dist/HttpApi/Operations/HttpOperation.d.ts.map +1 -0
- package/dist/HttpApi/Operations/HttpOperation.js +2 -0
- package/dist/HttpApi/Operations/HttpOperation.js.map +1 -0
- package/dist/HttpApi/Operations/Identity.d.ts +22 -0
- package/dist/HttpApi/Operations/Identity.d.ts.map +1 -0
- package/dist/HttpApi/Operations/Identity.js +5 -0
- package/dist/HttpApi/Operations/Identity.js.map +1 -0
- package/dist/HttpApi/Operations/Jwt.d.ts +14 -0
- package/dist/HttpApi/Operations/Jwt.d.ts.map +1 -0
- package/dist/HttpApi/Operations/Jwt.js +5 -0
- package/dist/HttpApi/Operations/Jwt.js.map +1 -0
- package/dist/HttpApi/Operations/LoginApproval.d.ts +16 -0
- package/dist/HttpApi/Operations/LoginApproval.d.ts.map +1 -0
- package/dist/HttpApi/Operations/LoginApproval.js +5 -0
- package/dist/HttpApi/Operations/LoginApproval.js.map +1 -0
- package/dist/HttpApi/Operations/MagicLink.d.ts +14 -0
- package/dist/HttpApi/Operations/MagicLink.d.ts.map +1 -0
- package/dist/HttpApi/Operations/MagicLink.js +5 -0
- package/dist/HttpApi/Operations/MagicLink.js.map +1 -0
- package/dist/HttpApi/Operations/Mfa.d.ts +24 -0
- package/dist/HttpApi/Operations/Mfa.d.ts.map +1 -0
- package/dist/HttpApi/Operations/Mfa.js +5 -0
- package/dist/HttpApi/Operations/Mfa.js.map +1 -0
- package/dist/HttpApi/Operations/OAuth.d.ts +20 -0
- package/dist/HttpApi/Operations/OAuth.d.ts.map +1 -0
- package/dist/HttpApi/Operations/OAuth.js +5 -0
- package/dist/HttpApi/Operations/OAuth.js.map +1 -0
- package/dist/HttpApi/Operations/OAuthProviderAuthorization.d.ts +12 -0
- package/dist/HttpApi/Operations/OAuthProviderAuthorization.d.ts.map +1 -0
- package/dist/HttpApi/Operations/OAuthProviderAuthorization.js +5 -0
- package/dist/HttpApi/Operations/OAuthProviderAuthorization.js.map +1 -0
- package/dist/HttpApi/Operations/OAuthToken.d.ts +16 -0
- package/dist/HttpApi/Operations/OAuthToken.d.ts.map +1 -0
- package/dist/HttpApi/Operations/OAuthToken.js +5 -0
- package/dist/HttpApi/Operations/OAuthToken.js.map +1 -0
- package/dist/HttpApi/Operations/Passkey.d.ts +22 -0
- package/dist/HttpApi/Operations/Passkey.d.ts.map +1 -0
- package/dist/HttpApi/Operations/Passkey.js +5 -0
- package/dist/HttpApi/Operations/Passkey.js.map +1 -0
- package/dist/HttpApi/Operations/Password.d.ts +22 -0
- package/dist/HttpApi/Operations/Password.d.ts.map +1 -0
- package/dist/HttpApi/Operations/Password.js +5 -0
- package/dist/HttpApi/Operations/Password.js.map +1 -0
- package/dist/HttpApi/Operations/RecoveryCodes.d.ts +20 -0
- package/dist/HttpApi/Operations/RecoveryCodes.d.ts.map +1 -0
- package/dist/HttpApi/Operations/RecoveryCodes.js +5 -0
- package/dist/HttpApi/Operations/RecoveryCodes.js.map +1 -0
- package/dist/HttpApi/Operations/RefreshToken.d.ts +12 -0
- package/dist/HttpApi/Operations/RefreshToken.d.ts.map +1 -0
- package/dist/HttpApi/Operations/RefreshToken.js +5 -0
- package/dist/HttpApi/Operations/RefreshToken.js.map +1 -0
- package/dist/HttpApi/Operations/Security.d.ts +12 -0
- package/dist/HttpApi/Operations/Security.d.ts.map +1 -0
- package/dist/HttpApi/Operations/Security.js +5 -0
- package/dist/HttpApi/Operations/Security.js.map +1 -0
- package/dist/HttpApi/Operations/SecurityTimeline.d.ts +19 -0
- package/dist/HttpApi/Operations/SecurityTimeline.d.ts.map +1 -0
- package/dist/HttpApi/Operations/SecurityTimeline.js +7 -0
- package/dist/HttpApi/Operations/SecurityTimeline.js.map +1 -0
- package/dist/HttpApi/Operations/Session.d.ts +22 -0
- package/dist/HttpApi/Operations/Session.d.ts.map +1 -0
- package/dist/HttpApi/Operations/Session.js +5 -0
- package/dist/HttpApi/Operations/Session.js.map +1 -0
- package/dist/HttpApi/Operations/StepUp.d.ts +22 -0
- package/dist/HttpApi/Operations/StepUp.d.ts.map +1 -0
- package/dist/HttpApi/Operations/StepUp.js +5 -0
- package/dist/HttpApi/Operations/StepUp.js.map +1 -0
- package/dist/HttpApi/Operations/Totp.d.ts +20 -0
- package/dist/HttpApi/Operations/Totp.d.ts.map +1 -0
- package/dist/HttpApi/Operations/Totp.js +5 -0
- package/dist/HttpApi/Operations/Totp.js.map +1 -0
- package/dist/HttpApi/Operations/TrustedDevice.d.ts +23 -0
- package/dist/HttpApi/Operations/TrustedDevice.d.ts.map +1 -0
- package/dist/HttpApi/Operations/TrustedDevice.js +7 -0
- package/dist/HttpApi/Operations/TrustedDevice.js.map +1 -0
- package/dist/HttpApi/Operations/index.d.ts +27 -0
- package/dist/HttpApi/Operations/index.d.ts.map +1 -0
- package/dist/HttpApi/Operations/index.js +27 -0
- package/dist/HttpApi/Operations/index.js.map +1 -0
- package/dist/HttpApi/Passkey.d.ts +5 -0
- package/dist/HttpApi/Passkey.d.ts.map +1 -0
- package/dist/HttpApi/Passkey.js +5 -0
- package/dist/HttpApi/Passkey.js.map +1 -0
- package/dist/HttpApi/Password.d.ts +220 -0
- package/dist/HttpApi/Password.d.ts.map +1 -0
- package/dist/HttpApi/Password.js +237 -0
- package/dist/HttpApi/Password.js.map +1 -0
- package/dist/HttpApi/PasswordContract.d.ts +307 -0
- package/dist/HttpApi/PasswordContract.d.ts.map +1 -0
- package/dist/HttpApi/PasswordContract.js +63 -0
- package/dist/HttpApi/PasswordContract.js.map +1 -0
- package/dist/HttpApi/PasswordGuards.d.ts +82 -0
- package/dist/HttpApi/PasswordGuards.d.ts.map +1 -0
- package/dist/HttpApi/PasswordGuards.js +179 -0
- package/dist/HttpApi/PasswordGuards.js.map +1 -0
- package/dist/HttpApi/RecoveryCodes.d.ts +5 -0
- package/dist/HttpApi/RecoveryCodes.d.ts.map +1 -0
- package/dist/HttpApi/RecoveryCodes.js +5 -0
- package/dist/HttpApi/RecoveryCodes.js.map +1 -0
- package/dist/HttpApi/RefreshToken.d.ts +4 -0
- package/dist/HttpApi/RefreshToken.d.ts.map +1 -0
- package/dist/HttpApi/RefreshToken.js +4 -0
- package/dist/HttpApi/RefreshToken.js.map +1 -0
- package/dist/HttpApi/RequestGuards.d.ts +42 -0
- package/dist/HttpApi/RequestGuards.d.ts.map +1 -0
- package/dist/HttpApi/RequestGuards.js +194 -0
- package/dist/HttpApi/RequestGuards.js.map +1 -0
- package/dist/HttpApi/Schemas.d.ts +361 -84
- package/dist/HttpApi/Schemas.d.ts.map +1 -1
- package/dist/HttpApi/Schemas.js +190 -27
- package/dist/HttpApi/Schemas.js.map +1 -1
- package/dist/HttpApi/Security.d.ts +12 -93
- package/dist/HttpApi/Security.d.ts.map +1 -1
- package/dist/HttpApi/Security.js +65 -356
- package/dist/HttpApi/Security.js.map +1 -1
- package/dist/HttpApi/SecurityTimeline.d.ts +4 -0
- package/dist/HttpApi/SecurityTimeline.d.ts.map +1 -0
- package/dist/HttpApi/SecurityTimeline.js +4 -0
- package/dist/HttpApi/SecurityTimeline.js.map +1 -0
- package/dist/HttpApi/Session.d.ts +5 -0
- package/dist/HttpApi/Session.d.ts.map +1 -0
- package/dist/HttpApi/Session.js +5 -0
- package/dist/HttpApi/Session.js.map +1 -0
- package/dist/HttpApi/SessionGuards.d.ts +15 -0
- package/dist/HttpApi/SessionGuards.d.ts.map +1 -0
- package/dist/HttpApi/SessionGuards.js +37 -0
- package/dist/HttpApi/SessionGuards.js.map +1 -0
- package/dist/HttpApi/StepUp.d.ts +5 -0
- package/dist/HttpApi/StepUp.d.ts.map +1 -0
- package/dist/HttpApi/StepUp.js +5 -0
- package/dist/HttpApi/StepUp.js.map +1 -0
- package/dist/HttpApi/Totp.d.ts +5 -0
- package/dist/HttpApi/Totp.d.ts.map +1 -0
- package/dist/HttpApi/Totp.js +5 -0
- package/dist/HttpApi/Totp.js.map +1 -0
- package/dist/HttpApi/Transport.d.ts +4 -6
- package/dist/HttpApi/Transport.d.ts.map +1 -1
- package/dist/HttpApi/Transport.js +62 -36
- package/dist/HttpApi/Transport.js.map +1 -1
- package/dist/HttpApi/TrustedDevice.d.ts +4 -0
- package/dist/HttpApi/TrustedDevice.d.ts.map +1 -0
- package/dist/HttpApi/TrustedDevice.js +4 -0
- package/dist/HttpApi/TrustedDevice.js.map +1 -0
- package/dist/HttpApi/index.d.ts +9 -3
- package/dist/HttpApi/index.d.ts.map +1 -1
- package/dist/HttpApi/index.js +8 -2
- package/dist/HttpApi/index.js.map +1 -1
- package/dist/Identity.d.ts +138 -0
- package/dist/Identity.d.ts.map +1 -0
- package/dist/Identity.js +105 -0
- package/dist/Identity.js.map +1 -0
- package/dist/IdentityManagement.d.ts +189 -0
- package/dist/IdentityManagement.d.ts.map +1 -0
- package/dist/IdentityManagement.js +317 -0
- package/dist/IdentityManagement.js.map +1 -0
- package/dist/IncidentAction.d.ts +1 -4
- package/dist/IncidentAction.d.ts.map +1 -1
- package/dist/IncidentAction.js +15 -9
- package/dist/IncidentAction.js.map +1 -1
- package/dist/Internal.d.ts +2 -0
- package/dist/Internal.d.ts.map +1 -1
- package/dist/Internal.js +30 -0
- package/dist/Internal.js.map +1 -1
- package/dist/Invitation.d.ts +362 -0
- package/dist/Invitation.d.ts.map +1 -0
- package/dist/Invitation.js +633 -0
- package/dist/Invitation.js.map +1 -0
- package/dist/IpQualityScore.d.ts +38 -0
- package/dist/IpQualityScore.d.ts.map +1 -0
- package/dist/IpQualityScore.js +215 -0
- package/dist/IpQualityScore.js.map +1 -0
- package/dist/Jwt.d.ts +8 -29
- package/dist/Jwt.d.ts.map +1 -1
- package/dist/Jwt.js +67 -56
- package/dist/Jwt.js.map +1 -1
- package/dist/LoginApproval.d.ts +15 -30
- package/dist/LoginApproval.d.ts.map +1 -1
- package/dist/LoginApproval.js +47 -41
- package/dist/LoginApproval.js.map +1 -1
- package/dist/LoginNotification.d.ts +7 -11
- package/dist/LoginNotification.d.ts.map +1 -1
- package/dist/LoginNotification.js +30 -17
- package/dist/LoginNotification.js.map +1 -1
- package/dist/LoginRisk.d.ts +11 -13
- package/dist/LoginRisk.d.ts.map +1 -1
- package/dist/LoginRisk.js +66 -26
- package/dist/LoginRisk.js.map +1 -1
- package/dist/LoginRiskEnrichment.d.ts +145 -0
- package/dist/LoginRiskEnrichment.d.ts.map +1 -0
- package/dist/LoginRiskEnrichment.js +220 -0
- package/dist/LoginRiskEnrichment.js.map +1 -0
- package/dist/MagicLink.d.ts +9 -8
- package/dist/MagicLink.d.ts.map +1 -1
- package/dist/MagicLink.js +40 -73
- package/dist/MagicLink.js.map +1 -1
- package/dist/Mailer.d.ts +19 -12
- package/dist/Mailer.d.ts.map +1 -1
- package/dist/Mailer.js +10 -12
- package/dist/Mailer.js.map +1 -1
- package/dist/OAuth.d.ts +93 -96
- package/dist/OAuth.d.ts.map +1 -1
- package/dist/OAuth.js +404 -217
- package/dist/OAuth.js.map +1 -1
- package/dist/Passkey.d.ts +15 -17
- package/dist/Passkey.d.ts.map +1 -1
- package/dist/Passkey.js +86 -39
- package/dist/Passkey.js.map +1 -1
- package/dist/PasskeyBrowser.d.ts +39 -0
- package/dist/PasskeyBrowser.d.ts.map +1 -0
- package/dist/PasskeyBrowser.js +206 -0
- package/dist/PasskeyBrowser.js.map +1 -0
- package/dist/PasskeySimpleWebAuthn.d.ts.map +1 -1
- package/dist/PasskeySimpleWebAuthn.js +19 -7
- package/dist/PasskeySimpleWebAuthn.js.map +1 -1
- package/dist/Password.d.ts +71 -37
- package/dist/Password.d.ts.map +1 -1
- package/dist/Password.js +295 -81
- package/dist/Password.js.map +1 -1
- package/dist/PasswordRisk.d.ts +63 -0
- package/dist/PasswordRisk.d.ts.map +1 -0
- package/dist/PasswordRisk.js +37 -0
- package/dist/PasswordRisk.js.map +1 -0
- package/dist/Permission.d.ts +308 -0
- package/dist/Permission.d.ts.map +1 -0
- package/dist/Permission.js +827 -0
- package/dist/Permission.js.map +1 -0
- package/dist/Policy.d.ts +31 -18
- package/dist/Policy.d.ts.map +1 -1
- package/dist/Policy.js +58 -14
- package/dist/Policy.js.map +1 -1
- package/dist/PostgresDelegatedAccessStorageMigrations.d.ts +7 -0
- package/dist/PostgresDelegatedAccessStorageMigrations.d.ts.map +1 -0
- package/dist/PostgresDelegatedAccessStorageMigrations.js +119 -0
- package/dist/PostgresDelegatedAccessStorageMigrations.js.map +1 -0
- package/dist/PostgresStorageMigrations.d.ts +15 -0
- package/dist/PostgresStorageMigrations.d.ts.map +1 -0
- package/dist/PostgresStorageMigrations.js +620 -0
- package/dist/PostgresStorageMigrations.js.map +1 -0
- package/dist/Privacy.d.ts +0 -3
- package/dist/Privacy.d.ts.map +1 -1
- package/dist/Privacy.js +2 -5
- package/dist/Privacy.js.map +1 -1
- package/dist/RateLimiter.d.ts +0 -3
- package/dist/RateLimiter.d.ts.map +1 -1
- package/dist/RateLimiter.js +1 -4
- package/dist/RateLimiter.js.map +1 -1
- package/dist/RecoveryCode.d.ts +83 -11
- package/dist/RecoveryCode.d.ts.map +1 -1
- package/dist/RecoveryCode.js +273 -75
- package/dist/RecoveryCode.js.map +1 -1
- package/dist/RecoveryPolicy.d.ts +81 -0
- package/dist/RecoveryPolicy.d.ts.map +1 -0
- package/dist/RecoveryPolicy.js +119 -0
- package/dist/RecoveryPolicy.js.map +1 -0
- package/dist/RefreshToken.d.ts +0 -9
- package/dist/RefreshToken.d.ts.map +1 -1
- package/dist/RefreshToken.js +31 -22
- package/dist/RefreshToken.js.map +1 -1
- package/dist/SecurityTimeline.d.ts +1 -4
- package/dist/SecurityTimeline.d.ts.map +1 -1
- package/dist/SecurityTimeline.js +110 -17
- package/dist/SecurityTimeline.js.map +1 -1
- package/dist/Sessions.d.ts +124 -29
- package/dist/Sessions.d.ts.map +1 -1
- package/dist/Sessions.js +626 -94
- package/dist/Sessions.js.map +1 -1
- package/dist/StepUp.d.ts +132 -11
- package/dist/StepUp.d.ts.map +1 -1
- package/dist/StepUp.js +387 -19
- package/dist/StepUp.js.map +1 -1
- package/dist/Storage.d.ts +89 -23
- package/dist/Storage.d.ts.map +1 -1
- package/dist/Storage.js +10 -12
- package/dist/Storage.js.map +1 -1
- package/dist/StorageMigrations.d.ts +6 -1
- package/dist/StorageMigrations.d.ts.map +1 -1
- package/dist/StorageMigrations.js +224 -0
- package/dist/StorageMigrations.js.map +1 -1
- package/dist/StorageSchemaGenerator.d.ts +486 -0
- package/dist/StorageSchemaGenerator.d.ts.map +1 -0
- package/dist/StorageSchemaGenerator.js +1022 -0
- package/dist/StorageSchemaGenerator.js.map +1 -0
- package/dist/StrongFactor.d.ts +64 -0
- package/dist/StrongFactor.d.ts.map +1 -0
- package/dist/StrongFactor.js +100 -0
- package/dist/StrongFactor.js.map +1 -0
- package/dist/Testing.js +2 -2
- package/dist/Testing.js.map +1 -1
- package/dist/Totp.d.ts +88 -13
- package/dist/Totp.d.ts.map +1 -1
- package/dist/Totp.js +192 -34
- package/dist/Totp.js.map +1 -1
- package/dist/TrustedDevice.d.ts +0 -9
- package/dist/TrustedDevice.d.ts.map +1 -1
- package/dist/TrustedDevice.js +15 -17
- package/dist/TrustedDevice.js.map +1 -1
- package/dist/WaitUntil.d.ts +0 -3
- package/dist/WaitUntil.d.ts.map +1 -1
- package/dist/WaitUntil.js +1 -4
- package/dist/WaitUntil.js.map +1 -1
- package/dist/Webhook.d.ts +42 -12
- package/dist/Webhook.d.ts.map +1 -1
- package/dist/Webhook.js +165 -26
- package/dist/Webhook.js.map +1 -1
- package/dist/alchemy/cloudflare/Email.d.ts.map +1 -1
- package/dist/alchemy/cloudflare/Email.js +9 -4
- package/dist/alchemy/cloudflare/Email.js.map +1 -1
- package/dist/cloudflare/Email.js +1 -1
- package/dist/cloudflare/Email.js.map +1 -1
- package/dist/cloudflare/RequestMetadata.d.ts +49 -0
- package/dist/cloudflare/RequestMetadata.d.ts.map +1 -0
- package/dist/cloudflare/RequestMetadata.js +71 -0
- package/dist/cloudflare/RequestMetadata.js.map +1 -0
- package/dist/index.d.ts +0 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -35
- package/dist/index.js.map +1 -1
- package/dist/internal/AuthFlowFinalizer.d.ts +31 -0
- package/dist/internal/AuthFlowFinalizer.d.ts.map +1 -0
- package/dist/internal/AuthFlowFinalizer.js +182 -0
- package/dist/internal/AuthFlowFinalizer.js.map +1 -0
- package/migrations/0001_auth_storage.sql +1 -0
- package/migrations/0006_auth_totp_factor.sql +1 -0
- package/migrations/0024_auth_permission.sql +39 -0
- package/migrations/0025_auth_permission_definition.sql +19 -0
- package/migrations/0026_auth_user_identity.sql +59 -0
- package/migrations/0027_auth_audit_log_storage_order.sql +46 -0
- package/migrations/0028_auth_oauth_client_secret_key.sql +39 -0
- package/migrations/delegated-access/0001_auth_delegated_access.sql +152 -0
- package/migrations/postgres/0001_auth_core.sql +77 -0
- package/migrations/postgres/0002_auth_security_oauth.sql +65 -0
- package/migrations/postgres/0003_auth_credentials_tokens.sql +43 -0
- package/migrations/postgres/0004_auth_security_history.sql +103 -0
- package/migrations/postgres/0005_auth_webhooks.sql +29 -0
- package/migrations/postgres/0006_auth_domain_timeline.sql +50 -0
- package/migrations/postgres/0007_auth_oauth_clients.sql +85 -0
- package/migrations/postgres/0008_auth_oauth_tokens.sql +63 -0
- package/migrations/postgres/0009_auth_permissions.sql +66 -0
- package/migrations/postgres/delegated-access/0001_auth_delegated_access.sql +112 -0
- package/package.json +301 -2
|
@@ -1,34 +1,59 @@
|
|
|
1
1
|
import { Data, Effect, Layer, Option, Redacted } from "effect";
|
|
2
|
-
import { Column, Query, Scalar, Table } from "effect-qb";
|
|
3
|
-
import { Executor, Query as SqliteQuery } from "effect-qb/sqlite";
|
|
4
|
-
import { AuditLogStore, AuditLogStoreError, auditEventActorUserId, auditEventUserId, filterAuditLogEvents, } from "./AuditLog.js";
|
|
2
|
+
import { Column, Executor as EffectQbExecutor, Query, Scalar, Table, } from "effect-qb";
|
|
3
|
+
import { Executor, Query as SqliteQuery, Renderer } from "effect-qb/sqlite";
|
|
5
4
|
import { ApiKeyId, ApiKeyPrefix, ApiKeySecretHash, ApiKeyStore, ApiKeyStoreError, } from "./ApiKey.js";
|
|
5
|
+
import { deriveAssurance, deriveMfaVerifiedAt, parseAuthenticationEvents, } from "./Assurance.js";
|
|
6
|
+
import { AtomicPlanError } from "./AtomicPlanExecutor.js";
|
|
7
|
+
import { AuditLogStore, AuditLogStoreError, auditEventActorUserId, auditEventUserId, decodeAuditEvent, filterAuditLogEvents, } from "./AuditLog.js";
|
|
6
8
|
import { DomainName, DomainVerificationId, DomainVerificationProofToken, DomainVerificationStore, DomainVerificationStoreError, } from "./DomainVerification.js";
|
|
7
|
-
import { AuthFlowId, ChallengeId, CredentialId, Email, OAuthAccountId, OAuthClientId, OAuthProviderId, SessionId, UnixMillis, UserId, } from "./Identifiers.js";
|
|
9
|
+
import { AuthFlowId, ChallengeId, CredentialId, Email, IdentityId, OAuthAccountId, OAuthClientId, OAuthProviderId, PermissionId, RoleId, SessionId, UnixMillis, UserId, } from "./Identifiers.js";
|
|
10
|
+
import { isIdentityLoginEligible } from "./Identity.js";
|
|
8
11
|
import { currentUnixMillis } from "./Internal.js";
|
|
9
12
|
import { JwtError, JwtRevocationStore } from "./Jwt.js";
|
|
10
13
|
import { LoginApprovalReviewError, LoginApprovalReviewStore, } from "./LoginApproval.js";
|
|
11
14
|
import { LoginRiskContext, LoginRiskHistory, LoginRiskHistoryError, LoginRiskSignal, maxLoginRiskLevel, } from "./LoginRisk.js";
|
|
12
|
-
import { OAuthAccountStore, OAuthAccountStoreError, OAuthAuthorizationCodeStore, OAuthAuthorizationCodeStoreError, OAuthClientStore, OAuthClientStoreError, OAuthConsentStore, OAuthConsentStoreError, OAuthClientSecretHash, OAuthClientSecretPrefix, OAuthClientSecretStore, OAuthClientSecretStoreError, OAuthCodeChallenge, OAuthProviderModeTokenStore, OAuthProviderModeTokenStoreError, OAuthProviderTokenVault, OAuthProviderTokenVaultError, } from "./OAuth.js";
|
|
15
|
+
import { OAuthAccountStore, OAuthAccountStoreError, OAuthIdentityBridgeConflictError, OAuthIdentityBridgeStore, OAuthIdentityBridgeStoreError, OAuthAuthorizationCodeStore, OAuthAuthorizationCodeStoreError, OAuthClientStore, OAuthClientStoreError, OAuthConsentStore, OAuthConsentStoreError, OAuthClientSecretHash, OAuthClientSecretPrefix, OAuthClientSecretStore, OAuthClientSecretStoreError, OAuthCodeChallenge, OAuthProviderModeTokenStore, OAuthProviderModeTokenStoreError, OAuthProviderTokenVault, OAuthProviderTokenVaultError, } from "./OAuth.js";
|
|
13
16
|
import { PasskeyCredentialId, PasskeyCredentialStore, PasskeyCredentialStoreError, } from "./Passkey.js";
|
|
14
|
-
import {
|
|
17
|
+
import { canonicalPermissionScope, normalizePermissionDefinitionListLimit, PermissionStore, PermissionStoreError, } from "./Permission.js";
|
|
18
|
+
import { RecoveryCodeHash, RecoveryCodeLoginCommitStore, RecoveryCodeLoginCommitStoreError, RecoveryCodeStore, RecoveryCodeStoreError, RecoveryCodeSessionRotateStore, RecoveryCodeSessionRotateStoreError, } from "./RecoveryCode.js";
|
|
15
19
|
import { RefreshTokenFamilyId, RefreshTokenId, RefreshTokenSecretHash, RefreshTokenStore, RefreshTokenStoreError, } from "./RefreshToken.js";
|
|
16
20
|
import { filterSecurityTimelineEvents, SecurityTimelineEventId, SecurityTimelineStore, SecurityTimelineStoreError, } from "./SecurityTimeline.js";
|
|
17
|
-
import { CredentialStore, SessionStore, StorageError, UserStore, VerificationStore, } from "./Storage.js";
|
|
18
|
-
import { TotpFactorStore, TotpFactorStoreError, TotpSecret, } from "./Totp.js";
|
|
21
|
+
import { CredentialStore, IdentityConflictError, IdentityStore, RegistrationConflictError, RegistrationFailureError, RegistrationStore, SessionStore, StorageError, UserStore, VerificationStore, } from "./Storage.js";
|
|
22
|
+
import { TotpFactorStore, TotpFactorStoreError, TotpLoginCommitStore, TotpLoginCommitStoreError, TotpSessionRotateStore, TotpSessionRotateStoreError, TotpSecret, } from "./Totp.js";
|
|
19
23
|
import { filterTrustedDevicesForUser, TrustedDeviceError, TrustedDeviceStore, } from "./TrustedDevice.js";
|
|
20
24
|
import { filterDueWebhookOutboxRecords, WebhookOutboxItemId, WebhookOutboxStore, WebhookOutboxStoreError, WebhookReplayStore, WebhookReplayStoreError, } from "./Webhook.js";
|
|
25
|
+
const boundColumnName = (expression) => {
|
|
26
|
+
const state = expression[Symbol.for("effect-qb/BoundColumn")];
|
|
27
|
+
if (typeof state?.columnName !== "string") {
|
|
28
|
+
throw new TypeError("Atomic SQLite storage requires bound table columns");
|
|
29
|
+
}
|
|
30
|
+
return state.columnName;
|
|
31
|
+
};
|
|
21
32
|
export class D1EffectQbError extends Data.TaggedError("D1EffectQbError") {
|
|
22
33
|
}
|
|
23
34
|
const makeAuthUserTable = (name) => Table.make(name, {
|
|
24
35
|
id: Column.text().pipe(Column.primaryKey),
|
|
25
|
-
email: Column.text(),
|
|
26
|
-
email_verified: Column.int(),
|
|
27
36
|
created_at: Column.int(),
|
|
28
37
|
updated_at: Column.int(),
|
|
29
38
|
disabled_at: Column.int().pipe(Column.nullable),
|
|
30
39
|
metadata: Column.text().pipe(Column.nullable),
|
|
31
40
|
});
|
|
41
|
+
const makeAuthUserIdentityTable = (name) => Table.make(name, {
|
|
42
|
+
id: Column.text().pipe(Column.primaryKey),
|
|
43
|
+
user_id: Column.text(),
|
|
44
|
+
scope_type: Column.text(),
|
|
45
|
+
scope_id: Column.text(),
|
|
46
|
+
kind: Column.text(),
|
|
47
|
+
value: Column.text(),
|
|
48
|
+
normalized_value: Column.text(),
|
|
49
|
+
verified_at: Column.int().pipe(Column.nullable),
|
|
50
|
+
is_primary_login: Column.int(),
|
|
51
|
+
created_at: Column.int(),
|
|
52
|
+
updated_at: Column.int(),
|
|
53
|
+
revoked_at: Column.int().pipe(Column.nullable),
|
|
54
|
+
replaced_by_id: Column.text().pipe(Column.nullable),
|
|
55
|
+
metadata: Column.text().pipe(Column.nullable),
|
|
56
|
+
});
|
|
32
57
|
const makeAuthCredentialTable = (name) => Table.make(name, {
|
|
33
58
|
id: Column.text().pipe(Column.primaryKey),
|
|
34
59
|
user_id: Column.text(),
|
|
@@ -46,6 +71,7 @@ const makeAuthSessionTable = (name) => Table.make(name, {
|
|
|
46
71
|
created_at: Column.int(),
|
|
47
72
|
expires_at: Column.int(),
|
|
48
73
|
auth_time: Column.int(),
|
|
74
|
+
authentication_events: Column.text(),
|
|
49
75
|
aal: Column.text(),
|
|
50
76
|
amr: Column.text(),
|
|
51
77
|
mfa_verified_at: Column.int().pipe(Column.nullable),
|
|
@@ -65,6 +91,7 @@ const makeAuthVerificationTable = (name) => Table.make(name, {
|
|
|
65
91
|
consumed_at: Column.int().pipe(Column.nullable),
|
|
66
92
|
});
|
|
67
93
|
const makeAuthAuditLogTable = (name) => Table.make(name, {
|
|
94
|
+
storage_id: Column.int().pipe(Column.primaryKey),
|
|
68
95
|
id: Column.text().pipe(Column.nullable),
|
|
69
96
|
type: Column.text(),
|
|
70
97
|
user_id: Column.text().pipe(Column.nullable),
|
|
@@ -142,6 +169,7 @@ const makeAuthTotpFactorTable = (name) => Table.make(name, {
|
|
|
142
169
|
created_at: Column.int(),
|
|
143
170
|
confirmed_at: Column.int().pipe(Column.nullable),
|
|
144
171
|
last_used_at: Column.int().pipe(Column.nullable),
|
|
172
|
+
last_accepted_counter: Column.int().pipe(Column.nullable),
|
|
145
173
|
revoked_at: Column.int().pipe(Column.nullable),
|
|
146
174
|
metadata: Column.text().pipe(Column.nullable),
|
|
147
175
|
});
|
|
@@ -250,7 +278,7 @@ const makeAuthOAuthAuthorizationCodeTable = (name) => Table.make(name, {
|
|
|
250
278
|
metadata: Column.text().pipe(Column.nullable),
|
|
251
279
|
});
|
|
252
280
|
const makeAuthOAuthClientSecretTable = (name) => Table.make(name, {
|
|
253
|
-
prefix: Column.text()
|
|
281
|
+
prefix: Column.text(),
|
|
254
282
|
client_id: Column.text(),
|
|
255
283
|
secret_hash: Column.text(),
|
|
256
284
|
authentication_methods: Column.text(),
|
|
@@ -320,6 +348,52 @@ const makeAuthWebhookReplayTable = (name) => Table.make(name, {
|
|
|
320
348
|
expires_at: Column.int(),
|
|
321
349
|
created_at: Column.int(),
|
|
322
350
|
});
|
|
351
|
+
const makeAuthPermissionDefinitionTable = (name) => Table.make(name, {
|
|
352
|
+
id: Column.text().pipe(Column.primaryKey),
|
|
353
|
+
description: Column.text().pipe(Column.nullable),
|
|
354
|
+
scope_type_present: Column.int(),
|
|
355
|
+
scope_type: Column.text(),
|
|
356
|
+
created_at: Column.int(),
|
|
357
|
+
updated_at: Column.int(),
|
|
358
|
+
disabled_at: Column.int().pipe(Column.nullable),
|
|
359
|
+
deleted_at: Column.int().pipe(Column.nullable),
|
|
360
|
+
});
|
|
361
|
+
const makeAuthRoleDefinitionTable = (name) => Table.make(name, {
|
|
362
|
+
id: Column.text().pipe(Column.primaryKey),
|
|
363
|
+
description: Column.text().pipe(Column.nullable),
|
|
364
|
+
created_at: Column.int(),
|
|
365
|
+
updated_at: Column.int(),
|
|
366
|
+
disabled_at: Column.int().pipe(Column.nullable),
|
|
367
|
+
deleted_at: Column.int().pipe(Column.nullable),
|
|
368
|
+
});
|
|
369
|
+
const makeAuthPermissionGrantTable = (name) => Table.make(name, {
|
|
370
|
+
subject_type: Column.text(),
|
|
371
|
+
subject_id: Column.text(),
|
|
372
|
+
permission_id: Column.text(),
|
|
373
|
+
scope_type: Column.text(),
|
|
374
|
+
scope_id_present: Column.int(),
|
|
375
|
+
scope_id: Column.text(),
|
|
376
|
+
expires_at: Column.int().pipe(Column.nullable),
|
|
377
|
+
metadata: Column.text().pipe(Column.nullable),
|
|
378
|
+
revoked_at: Column.int().pipe(Column.nullable),
|
|
379
|
+
});
|
|
380
|
+
const makeAuthRoleGrantTable = (name) => Table.make(name, {
|
|
381
|
+
subject_type: Column.text(),
|
|
382
|
+
subject_id: Column.text(),
|
|
383
|
+
role_id: Column.text(),
|
|
384
|
+
scope_type: Column.text(),
|
|
385
|
+
scope_id_present: Column.int(),
|
|
386
|
+
scope_id: Column.text(),
|
|
387
|
+
expires_at: Column.int().pipe(Column.nullable),
|
|
388
|
+
metadata: Column.text().pipe(Column.nullable),
|
|
389
|
+
revoked_at: Column.int().pipe(Column.nullable),
|
|
390
|
+
});
|
|
391
|
+
const makeAuthRolePermissionTable = (name) => Table.make(name, {
|
|
392
|
+
role_id: Column.text(),
|
|
393
|
+
permission_id: Column.text(),
|
|
394
|
+
scope_type_present: Column.int(),
|
|
395
|
+
scope_type: Column.text(),
|
|
396
|
+
});
|
|
323
397
|
const authSqliteTablesTypeId = Symbol.for("@effect-auth/core/EffectQbSqliteStorage/AuthSqliteTables");
|
|
324
398
|
const jsonEncode = (value) => value === undefined ? null : JSON.stringify(value);
|
|
325
399
|
const jsonDecode = (value) => {
|
|
@@ -346,14 +420,50 @@ const decodeBoolean = (value) => {
|
|
|
346
420
|
};
|
|
347
421
|
const mutationValues = (value) => value;
|
|
348
422
|
const queryInsert = (table, values) => Query.insert(table, values);
|
|
423
|
+
const queryInsertMany = (table, values) => Query.from(Query.values(values))(Query.insert(table));
|
|
349
424
|
const queryUpdate = (table, values) => Query.update(table, values);
|
|
350
425
|
const queryEq = (left, right) => Query.eq(left, right);
|
|
426
|
+
const queryNeq = (left, right) => Query.neq(left, right);
|
|
351
427
|
const queryGt = (left, right) => Query.gt(left, right);
|
|
352
428
|
const queryLt = (left, right) => Query.lt(left, right);
|
|
353
429
|
const queryLte = (left, right) => Query.lte(left, right);
|
|
354
430
|
const queryIsNull = (value) => Query.isNull(value);
|
|
431
|
+
const queryIsNotNull = (value) => Query.isNotNull(value);
|
|
432
|
+
const queryIsNotDistinctFrom = (left, right) => Query.isNotDistinctFrom(left, right);
|
|
433
|
+
const queryAnd = (...values) => Query.and(...values);
|
|
434
|
+
const queryOr = (...values) => Query.or(...values);
|
|
435
|
+
const queryNot = (value) => Query.not(value);
|
|
436
|
+
const queryExists = (plan) => Query.exists(plan);
|
|
437
|
+
const queryScalar = (plan) => Query.scalar(plan);
|
|
438
|
+
const queryChanges = () => Query.call("changes");
|
|
439
|
+
const queryChangedOne = () => queryEq(queryChanges(), 1);
|
|
440
|
+
const queryWithoutNullRefinement = (value) => Query.cast(value, value[Scalar.TypeId].dbType);
|
|
355
441
|
const queryIn = (value, first, ...rest) => Query.in(value, first, ...rest);
|
|
442
|
+
const aliasTable = (table, alias) => Table.alias(table, alias);
|
|
443
|
+
const aliasColumn = (table, column) => table[boundColumnName(column)];
|
|
444
|
+
const queryAstTypeId = Symbol.for("effect-qb/QueryAst");
|
|
445
|
+
const targetMutationExpression = (table, columnName, value) => {
|
|
446
|
+
if (value === null) {
|
|
447
|
+
return Query.literal(null);
|
|
448
|
+
}
|
|
449
|
+
const plan = queryUpdate(table, { [columnName]: value });
|
|
450
|
+
const ast = plan[queryAstTypeId];
|
|
451
|
+
const assignment = ast?.set?.find((candidate) => candidate.columnName === columnName);
|
|
452
|
+
if (assignment === undefined) {
|
|
453
|
+
throw new TypeError(`Invalid SQLite mutation column: ${columnName}`);
|
|
454
|
+
}
|
|
455
|
+
return assignment.value;
|
|
456
|
+
};
|
|
457
|
+
const queryInsertWhere = (table, values, predicate) => {
|
|
458
|
+
const selection = Object.fromEntries(Object.entries(values).map(([key, value]) => [
|
|
459
|
+
key,
|
|
460
|
+
targetMutationExpression(table, key, value),
|
|
461
|
+
]));
|
|
462
|
+
const select = Query.where(predicate)(Query.select(selection));
|
|
463
|
+
return Query.from(select)(Query.insert(table));
|
|
464
|
+
};
|
|
356
465
|
const executePlan = (executor, plan) => executor.execute(plan);
|
|
466
|
+
const executeAtomicPlans = (atomic, plans) => atomic.execute(plans);
|
|
357
467
|
const omitUndefined = (record) => Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
|
|
358
468
|
const sessionMetadataEnvelopeKey = "__effectAuthSession";
|
|
359
469
|
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -426,13 +536,31 @@ const encodeSessionMetadata = (row) => {
|
|
|
426
536
|
};
|
|
427
537
|
const decodeDefaultUserRow = (row) => omitUndefined({
|
|
428
538
|
id: UserId(row.id),
|
|
429
|
-
email: Email(row.email),
|
|
430
|
-
emailVerified: decodeBoolean(row.emailVerified),
|
|
431
539
|
createdAt: UnixMillis(Number(row.createdAt)),
|
|
432
540
|
updatedAt: UnixMillis(Number(row.updatedAt)),
|
|
433
541
|
disabledAt: optionalUnixMillis(row.disabledAt),
|
|
434
542
|
metadata: jsonDecode(row.metadata),
|
|
435
543
|
});
|
|
544
|
+
const decodeDefaultUserIdentityRow = (row) => omitUndefined({
|
|
545
|
+
id: IdentityId(row.id),
|
|
546
|
+
userId: UserId(row.userId),
|
|
547
|
+
scope: row.scopeType === "global"
|
|
548
|
+
? { type: "global" }
|
|
549
|
+
: { type: "tenant", tenantId: row.scopeId },
|
|
550
|
+
kind: row.kind,
|
|
551
|
+
value: row.value,
|
|
552
|
+
normalizedValue: row.normalizedValue,
|
|
553
|
+
verifiedAt: optionalUnixMillis(row.verifiedAt),
|
|
554
|
+
isPrimaryLogin: decodeBoolean(row.isPrimaryLogin),
|
|
555
|
+
createdAt: UnixMillis(Number(row.createdAt)),
|
|
556
|
+
updatedAt: UnixMillis(Number(row.updatedAt)),
|
|
557
|
+
revokedAt: optionalUnixMillis(row.revokedAt),
|
|
558
|
+
replacedById: row.replacedById === null ? undefined : IdentityId(row.replacedById),
|
|
559
|
+
metadata: jsonDecode(row.metadata),
|
|
560
|
+
});
|
|
561
|
+
const decodeUserIdentityRow = (tables, row) => tables.identity.decode === undefined
|
|
562
|
+
? decodeDefaultUserIdentityRow(row)
|
|
563
|
+
: tables.identity.decode(row);
|
|
436
564
|
const decodeUserRow = (tables, row) => tables.user.decode === undefined
|
|
437
565
|
? decodeDefaultUserRow(row)
|
|
438
566
|
: tables.user.decode(row);
|
|
@@ -450,6 +578,34 @@ const decodePasswordCredentialRow = (tables, row) => tables.credential.decodePas
|
|
|
450
578
|
: tables.credential.decodePassword(row);
|
|
451
579
|
const decodeDefaultSessionRow = (row) => {
|
|
452
580
|
const sessionMetadata = decodeSessionMetadata(row.metadata);
|
|
581
|
+
const authenticationEvents = parseAuthenticationEvents(jsonDecode(row.authenticationEvents));
|
|
582
|
+
if (authenticationEvents.some((event) => event.type === "custom")) {
|
|
583
|
+
return omitUndefined({
|
|
584
|
+
id: SessionId(row.id),
|
|
585
|
+
userId: UserId(row.userId),
|
|
586
|
+
secretHash: row.secretHash,
|
|
587
|
+
createdAt: UnixMillis(Number(row.createdAt)),
|
|
588
|
+
expiresAt: UnixMillis(Number(row.expiresAt)),
|
|
589
|
+
authTime: UnixMillis(Number(row.authTime)),
|
|
590
|
+
authenticationEvents,
|
|
591
|
+
aal: row.aal,
|
|
592
|
+
amr: decodeAmr(row.amr),
|
|
593
|
+
mfaVerifiedAt: optionalUnixMillis(row.mfaVerifiedAt),
|
|
594
|
+
claims: sessionMetadata.claims,
|
|
595
|
+
metadata: sessionMetadata.metadata,
|
|
596
|
+
revokedAt: optionalUnixMillis(row.revokedAt),
|
|
597
|
+
lastSeenAt: optionalUnixMillis(row.lastSeenAt),
|
|
598
|
+
rotatedAt: optionalUnixMillis(row.rotatedAt),
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
const assurance = deriveAssurance(authenticationEvents);
|
|
602
|
+
const mfaVerifiedAt = deriveMfaVerifiedAt(authenticationEvents);
|
|
603
|
+
if (row.aal !== assurance.level ||
|
|
604
|
+
JSON.stringify(decodeAmr(row.amr)) !== JSON.stringify(assurance.amr) ||
|
|
605
|
+
optionalUnixMillis(row.mfaVerifiedAt) !== mfaVerifiedAt ||
|
|
606
|
+
Number(row.authTime) !== Number(assurance.authenticatedAt)) {
|
|
607
|
+
throw new Error("Session assurance summary does not match authentication events");
|
|
608
|
+
}
|
|
453
609
|
return omitUndefined({
|
|
454
610
|
id: SessionId(row.id),
|
|
455
611
|
userId: UserId(row.userId),
|
|
@@ -457,6 +613,7 @@ const decodeDefaultSessionRow = (row) => {
|
|
|
457
613
|
createdAt: UnixMillis(Number(row.createdAt)),
|
|
458
614
|
expiresAt: UnixMillis(Number(row.expiresAt)),
|
|
459
615
|
authTime: UnixMillis(Number(row.authTime)),
|
|
616
|
+
authenticationEvents,
|
|
460
617
|
aal: row.aal,
|
|
461
618
|
amr: decodeAmr(row.amr),
|
|
462
619
|
mfaVerifiedAt: optionalUnixMillis(row.mfaVerifiedAt),
|
|
@@ -467,9 +624,13 @@ const decodeDefaultSessionRow = (row) => {
|
|
|
467
624
|
rotatedAt: optionalUnixMillis(row.rotatedAt),
|
|
468
625
|
});
|
|
469
626
|
};
|
|
470
|
-
const decodeSessionRow = (tables, row) =>
|
|
471
|
-
|
|
472
|
-
|
|
627
|
+
const decodeSessionRow = (tables, row) => {
|
|
628
|
+
const decoded = tables.session.decode === undefined
|
|
629
|
+
? decodeDefaultSessionRow(row)
|
|
630
|
+
: tables.session.decode(row);
|
|
631
|
+
assertSessionAssurance(decoded);
|
|
632
|
+
return decoded;
|
|
633
|
+
};
|
|
473
634
|
const decodeDefaultVerificationRow = (row) => omitUndefined({
|
|
474
635
|
id: ChallengeId(row.id),
|
|
475
636
|
type: row.type,
|
|
@@ -483,7 +644,7 @@ const decodeDefaultVerificationRow = (row) => omitUndefined({
|
|
|
483
644
|
const decodeVerificationRow = (tables, row) => tables.verification.decode === undefined
|
|
484
645
|
? decodeDefaultVerificationRow(row)
|
|
485
646
|
: tables.verification.decode(row);
|
|
486
|
-
const decodeDefaultAuditLogEvent = (row) => jsonDecode(row.event);
|
|
647
|
+
const decodeDefaultAuditLogEvent = (row) => decodeAuditEvent(jsonDecode(row.event));
|
|
487
648
|
const decodeAuditLogEvent = (tables, row) => tables.auditLog.decode === undefined
|
|
488
649
|
? decodeDefaultAuditLogEvent(row)
|
|
489
650
|
: tables.auditLog.decode(row);
|
|
@@ -523,7 +684,9 @@ const decodeDefaultLoginRiskHistoryRecord = (row) => omitUndefined({
|
|
|
523
684
|
locationKey: row.locationKey ?? undefined,
|
|
524
685
|
country: row.country ?? undefined,
|
|
525
686
|
region: row.region ?? undefined,
|
|
526
|
-
latitude: row.latitudeMicro === null
|
|
687
|
+
latitude: row.latitudeMicro === null
|
|
688
|
+
? undefined
|
|
689
|
+
: Number(row.latitudeMicro) / 1_000_000,
|
|
527
690
|
longitude: row.longitudeMicro === null
|
|
528
691
|
? undefined
|
|
529
692
|
: Number(row.longitudeMicro) / 1_000_000,
|
|
@@ -546,7 +709,9 @@ const decodeTrustedDeviceRow = (tables, row) => tables.trustedDevice.decode ===
|
|
|
546
709
|
: tables.trustedDevice.decode(row);
|
|
547
710
|
const decodePasskeyTransports = (value) => {
|
|
548
711
|
const decoded = jsonDecode(value);
|
|
549
|
-
return Array.isArray(decoded)
|
|
712
|
+
return Array.isArray(decoded)
|
|
713
|
+
? decoded
|
|
714
|
+
: undefined;
|
|
550
715
|
};
|
|
551
716
|
const decodeDefaultPasskeyCredentialRow = (row) => omitUndefined({
|
|
552
717
|
id: CredentialId(row.id),
|
|
@@ -576,6 +741,9 @@ const decodeDefaultTotpFactorRow = (row) => omitUndefined({
|
|
|
576
741
|
createdAt: UnixMillis(Number(row.createdAt)),
|
|
577
742
|
confirmedAt: optionalUnixMillis(row.confirmedAt),
|
|
578
743
|
lastUsedAt: optionalUnixMillis(row.lastUsedAt),
|
|
744
|
+
lastAcceptedCounter: row.lastAcceptedCounter === null
|
|
745
|
+
? undefined
|
|
746
|
+
: Number(row.lastAcceptedCounter),
|
|
579
747
|
revokedAt: optionalUnixMillis(row.revokedAt),
|
|
580
748
|
metadata: jsonDecode(row.metadata),
|
|
581
749
|
});
|
|
@@ -712,7 +880,8 @@ const decodeOAuthConsentRow = (tables, row) => tables.oauthConsent.decode === un
|
|
|
712
880
|
: tables.oauthConsent.decode(row);
|
|
713
881
|
const oauthConsentCoversScopes = (record, scopes = []) => scopes.every((scope) => record.scopes.includes(scope));
|
|
714
882
|
const isOAuthConsentActive = (record, input) => record.revokedAt === undefined &&
|
|
715
|
-
(record.expiresAt === undefined ||
|
|
883
|
+
(record.expiresAt === undefined ||
|
|
884
|
+
Number(record.expiresAt) > Number(input.now)) &&
|
|
716
885
|
oauthConsentCoversScopes(record, input.scopes);
|
|
717
886
|
const oauthClientRedirectUriValidationResult = (client, input) => {
|
|
718
887
|
if (client === undefined) {
|
|
@@ -740,7 +909,9 @@ const decodeDefaultOAuthAuthorizationCodeRow = (row) => omitUndefined({
|
|
|
740
909
|
subject: row.subject,
|
|
741
910
|
redirectUri: row.redirectUri,
|
|
742
911
|
scopes: decodeStringArray(row.scopes),
|
|
743
|
-
codeChallenge: row.codeChallenge === null
|
|
912
|
+
codeChallenge: row.codeChallenge === null
|
|
913
|
+
? undefined
|
|
914
|
+
: OAuthCodeChallenge(row.codeChallenge),
|
|
744
915
|
codeChallengeMethod: row.codeChallengeMethod === null
|
|
745
916
|
? undefined
|
|
746
917
|
: row.codeChallengeMethod,
|
|
@@ -840,6 +1011,82 @@ const decodeDefaultWebhookOutboxRecord = (row) => omitUndefined({
|
|
|
840
1011
|
const decodeWebhookOutboxRecord = (tables, row) => tables.webhookOutbox.decode === undefined
|
|
841
1012
|
? decodeDefaultWebhookOutboxRecord(row)
|
|
842
1013
|
: tables.webhookOutbox.decode(row);
|
|
1014
|
+
const decodeDefaultPermissionDefinition = (row) => omitUndefined({
|
|
1015
|
+
id: PermissionId(row.id),
|
|
1016
|
+
description: row.description ?? undefined,
|
|
1017
|
+
scopeType: decodeBoolean(row.scopeTypePresent) ? row.scopeType : undefined,
|
|
1018
|
+
createdAt: UnixMillis(Number(row.createdAt)),
|
|
1019
|
+
updatedAt: UnixMillis(Number(row.updatedAt)),
|
|
1020
|
+
disabledAt: optionalUnixMillis(row.disabledAt),
|
|
1021
|
+
deletedAt: optionalUnixMillis(row.deletedAt),
|
|
1022
|
+
});
|
|
1023
|
+
const decodePermissionDefinition = (tables, row) => tables.permissionDefinition.decode === undefined
|
|
1024
|
+
? decodeDefaultPermissionDefinition(row)
|
|
1025
|
+
: tables.permissionDefinition.decode(row);
|
|
1026
|
+
const decodeDefaultRoleDefinition = (row) => omitUndefined({
|
|
1027
|
+
id: RoleId(row.id),
|
|
1028
|
+
description: row.description ?? undefined,
|
|
1029
|
+
createdAt: UnixMillis(Number(row.createdAt)),
|
|
1030
|
+
updatedAt: UnixMillis(Number(row.updatedAt)),
|
|
1031
|
+
disabledAt: optionalUnixMillis(row.disabledAt),
|
|
1032
|
+
deletedAt: optionalUnixMillis(row.deletedAt),
|
|
1033
|
+
});
|
|
1034
|
+
const decodeRoleDefinition = (tables, row) => tables.roleDefinition.decode === undefined
|
|
1035
|
+
? decodeDefaultRoleDefinition(row)
|
|
1036
|
+
: tables.roleDefinition.decode(row);
|
|
1037
|
+
const decodePermissionScope = (scopeType, scopeIdPresent, scopeId) => {
|
|
1038
|
+
const hasId = decodeBoolean(scopeIdPresent);
|
|
1039
|
+
if (scopeType === "global" && !hasId) {
|
|
1040
|
+
return undefined;
|
|
1041
|
+
}
|
|
1042
|
+
return hasId ? { type: scopeType, id: scopeId } : { type: scopeType };
|
|
1043
|
+
};
|
|
1044
|
+
const decodeDefaultPermissionGrant = (row) => omitUndefined({
|
|
1045
|
+
subject: { type: row.subjectType, id: row.subjectId },
|
|
1046
|
+
permission: PermissionId(row.permissionId),
|
|
1047
|
+
scope: decodePermissionScope(row.scopeType, row.scopeIdPresent, row.scopeId),
|
|
1048
|
+
expiresAt: optionalUnixMillis(row.expiresAt),
|
|
1049
|
+
metadata: jsonDecode(row.metadata),
|
|
1050
|
+
revokedAt: optionalUnixMillis(row.revokedAt),
|
|
1051
|
+
});
|
|
1052
|
+
const decodePermissionGrant = (tables, row) => {
|
|
1053
|
+
const decoded = tables.permissionGrant.decode === undefined
|
|
1054
|
+
? decodeDefaultPermissionGrant(row)
|
|
1055
|
+
: tables.permissionGrant.decode(row);
|
|
1056
|
+
const { scope: _scope, ...withoutScope } = decoded;
|
|
1057
|
+
const scope = canonicalPermissionScope(decoded.scope);
|
|
1058
|
+
return {
|
|
1059
|
+
...withoutScope,
|
|
1060
|
+
...(scope === undefined ? {} : { scope }),
|
|
1061
|
+
};
|
|
1062
|
+
};
|
|
1063
|
+
const decodeDefaultRoleGrant = (row) => omitUndefined({
|
|
1064
|
+
subject: { type: row.subjectType, id: row.subjectId },
|
|
1065
|
+
role: RoleId(row.roleId),
|
|
1066
|
+
scope: decodePermissionScope(row.scopeType, row.scopeIdPresent, row.scopeId),
|
|
1067
|
+
expiresAt: optionalUnixMillis(row.expiresAt),
|
|
1068
|
+
metadata: jsonDecode(row.metadata),
|
|
1069
|
+
revokedAt: optionalUnixMillis(row.revokedAt),
|
|
1070
|
+
});
|
|
1071
|
+
const decodeRoleGrant = (tables, row) => {
|
|
1072
|
+
const decoded = tables.roleGrant.decode === undefined
|
|
1073
|
+
? decodeDefaultRoleGrant(row)
|
|
1074
|
+
: tables.roleGrant.decode(row);
|
|
1075
|
+
const { scope: _scope, ...withoutScope } = decoded;
|
|
1076
|
+
const scope = canonicalPermissionScope(decoded.scope);
|
|
1077
|
+
return {
|
|
1078
|
+
...withoutScope,
|
|
1079
|
+
...(scope === undefined ? {} : { scope }),
|
|
1080
|
+
};
|
|
1081
|
+
};
|
|
1082
|
+
const decodeDefaultRolePermission = (row) => omitUndefined({
|
|
1083
|
+
role: RoleId(row.roleId),
|
|
1084
|
+
permission: PermissionId(row.permissionId),
|
|
1085
|
+
scopeType: decodeBoolean(row.scopeTypePresent) ? row.scopeType : undefined,
|
|
1086
|
+
});
|
|
1087
|
+
const decodeRolePermission = (tables, row) => tables.rolePermission.decode === undefined
|
|
1088
|
+
? decodeDefaultRolePermission(row)
|
|
1089
|
+
: tables.rolePermission.decode(row);
|
|
843
1090
|
const providerTokenVaultTokens = (record) => omitUndefined({
|
|
844
1091
|
accessTokenCiphertext: record.accessTokenCiphertext,
|
|
845
1092
|
refreshTokenCiphertext: record.refreshTokenCiphertext,
|
|
@@ -850,13 +1097,50 @@ const providerTokenVaultTokens = (record) => omitUndefined({
|
|
|
850
1097
|
});
|
|
851
1098
|
const userInsert = (row) => ({
|
|
852
1099
|
id: row.id,
|
|
853
|
-
email: row.email,
|
|
854
|
-
email_verified: row.emailVerified ? 1 : 0,
|
|
855
1100
|
created_at: Number(row.createdAt),
|
|
856
1101
|
updated_at: Number(row.updatedAt),
|
|
857
1102
|
disabled_at: row.disabledAt === undefined ? null : Number(row.disabledAt),
|
|
858
1103
|
metadata: jsonEncode(row.metadata),
|
|
859
1104
|
});
|
|
1105
|
+
const identityScopeId = (scope) => scope.type === "global" ? "global" : scope.tenantId;
|
|
1106
|
+
const userIdentityInsert = (row) => ({
|
|
1107
|
+
id: row.id,
|
|
1108
|
+
user_id: row.userId,
|
|
1109
|
+
scope_type: row.scope.type,
|
|
1110
|
+
scope_id: identityScopeId(row.scope),
|
|
1111
|
+
kind: row.kind,
|
|
1112
|
+
value: row.value,
|
|
1113
|
+
normalized_value: row.normalizedValue,
|
|
1114
|
+
verified_at: row.verifiedAt === undefined ? null : Number(row.verifiedAt),
|
|
1115
|
+
is_primary_login: row.isPrimaryLogin === true ? 1 : 0,
|
|
1116
|
+
created_at: Number(row.createdAt),
|
|
1117
|
+
updated_at: Number(row.createdAt),
|
|
1118
|
+
revoked_at: null,
|
|
1119
|
+
replaced_by_id: null,
|
|
1120
|
+
metadata: jsonEncode(row.metadata),
|
|
1121
|
+
});
|
|
1122
|
+
const userIdentityVerify = (input) => ({
|
|
1123
|
+
verified_at: Number(input.verifiedAt),
|
|
1124
|
+
updated_at: Number(input.verifiedAt),
|
|
1125
|
+
});
|
|
1126
|
+
const userIdentitySetPrimary = (input, primary) => ({
|
|
1127
|
+
is_primary_login: primary ? 1 : 0,
|
|
1128
|
+
updated_at: Number(input.updatedAt),
|
|
1129
|
+
});
|
|
1130
|
+
const userIdentityReplace = (input) => ({
|
|
1131
|
+
is_primary_login: 0,
|
|
1132
|
+
revoked_at: Number(input.replacedAt),
|
|
1133
|
+
replaced_by_id: input.replacement.id,
|
|
1134
|
+
updated_at: Number(input.replacedAt),
|
|
1135
|
+
});
|
|
1136
|
+
const userIdentityRevoke = (input, _row, metadata) => ({
|
|
1137
|
+
is_primary_login: 0,
|
|
1138
|
+
revoked_at: Number(input.revokedAt),
|
|
1139
|
+
updated_at: Number(input.revokedAt),
|
|
1140
|
+
metadata: input.reason === undefined
|
|
1141
|
+
? metadata
|
|
1142
|
+
: Query.call("json_set", Query.coalesce(metadata, "{}"), "$.revokeReason", input.reason),
|
|
1143
|
+
});
|
|
860
1144
|
const passwordCredentialInsert = (row) => ({
|
|
861
1145
|
id: row.id,
|
|
862
1146
|
user_id: row.userId,
|
|
@@ -871,21 +1155,40 @@ const passwordCredentialUpdatePassword = (input) => ({
|
|
|
871
1155
|
password_hash: input.passwordHash,
|
|
872
1156
|
updated_at: Number(input.updatedAt),
|
|
873
1157
|
});
|
|
874
|
-
const
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
}
|
|
1158
|
+
const assertSessionAssurance = (row) => {
|
|
1159
|
+
const authenticationEvents = parseAuthenticationEvents(row.authenticationEvents);
|
|
1160
|
+
// Sessions owns registry-aware validation for custom evidence.
|
|
1161
|
+
if (authenticationEvents.some((event) => event.type === "custom")) {
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
const assurance = deriveAssurance(authenticationEvents);
|
|
1165
|
+
if (assurance.authenticatedAt === undefined ||
|
|
1166
|
+
row.aal !== assurance.level ||
|
|
1167
|
+
JSON.stringify(row.amr) !== JSON.stringify(assurance.amr) ||
|
|
1168
|
+
row.mfaVerifiedAt !== deriveMfaVerifiedAt(authenticationEvents) ||
|
|
1169
|
+
Number(row.authTime) !== Number(assurance.authenticatedAt)) {
|
|
1170
|
+
throw new Error("Session assurance summary does not match authentication events");
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
const sessionInsert = (row) => {
|
|
1174
|
+
assertSessionAssurance(row);
|
|
1175
|
+
return {
|
|
1176
|
+
id: row.id,
|
|
1177
|
+
user_id: row.userId,
|
|
1178
|
+
secret_hash: row.secretHash,
|
|
1179
|
+
created_at: Number(row.createdAt),
|
|
1180
|
+
expires_at: Number(row.expiresAt),
|
|
1181
|
+
auth_time: Number(row.authTime),
|
|
1182
|
+
authentication_events: JSON.stringify(row.authenticationEvents),
|
|
1183
|
+
aal: row.aal,
|
|
1184
|
+
amr: JSON.stringify(row.amr),
|
|
1185
|
+
mfa_verified_at: row.mfaVerifiedAt === undefined ? null : Number(row.mfaVerifiedAt),
|
|
1186
|
+
metadata: encodeSessionMetadata(row),
|
|
1187
|
+
revoked_at: null,
|
|
1188
|
+
last_seen_at: null,
|
|
1189
|
+
rotated_at: null,
|
|
1190
|
+
};
|
|
1191
|
+
};
|
|
889
1192
|
const verificationInsert = (row) => ({
|
|
890
1193
|
id: row.id,
|
|
891
1194
|
type: row.type,
|
|
@@ -946,10 +1249,6 @@ const loginRiskHistoryInsert = (row) => ({
|
|
|
946
1249
|
risk_level: row.riskLevel ?? null,
|
|
947
1250
|
created_at: Number(row.createdAt),
|
|
948
1251
|
});
|
|
949
|
-
const userMarkEmailVerifiedUpdate = (input) => ({
|
|
950
|
-
email_verified: 1,
|
|
951
|
-
updated_at: Number(input.updatedAt),
|
|
952
|
-
});
|
|
953
1252
|
const sessionRefreshUpdate = (input) => omitUndefined({
|
|
954
1253
|
expires_at: Number(input.expiresAt),
|
|
955
1254
|
last_seen_at: input.lastSeenAt === undefined ? undefined : Number(input.lastSeenAt),
|
|
@@ -960,14 +1259,28 @@ const sessionRotateUpdate = (input) => omitUndefined({
|
|
|
960
1259
|
expires_at: input.expiresAt === undefined ? undefined : Number(input.expiresAt),
|
|
961
1260
|
});
|
|
962
1261
|
const sessionUpdateClaimsUpdate = (row, input) => omitUndefined({
|
|
963
|
-
aal: input.aal,
|
|
964
|
-
amr: input.amr === undefined ? undefined : JSON.stringify(input.amr),
|
|
965
|
-
mfa_verified_at: input.mfaVerifiedAt === undefined ? undefined : Number(input.mfaVerifiedAt),
|
|
966
1262
|
metadata: encodeSessionMetadata({
|
|
967
1263
|
...row,
|
|
968
1264
|
claims: input.claims ?? row.claims,
|
|
969
1265
|
}),
|
|
970
1266
|
});
|
|
1267
|
+
const sessionAppendEvidenceUpdate = (input) => {
|
|
1268
|
+
assertSessionAssurance(input);
|
|
1269
|
+
return {
|
|
1270
|
+
authentication_events: JSON.stringify(input.authenticationEvents),
|
|
1271
|
+
auth_time: Number(input.authTime),
|
|
1272
|
+
aal: input.aal,
|
|
1273
|
+
amr: JSON.stringify(input.amr),
|
|
1274
|
+
mfa_verified_at: input.mfaVerifiedAt === undefined ? null : Number(input.mfaVerifiedAt),
|
|
1275
|
+
};
|
|
1276
|
+
};
|
|
1277
|
+
const sessionAssureAndRotateUpdate = (row, input) => ({
|
|
1278
|
+
...sessionAppendEvidenceUpdate(input),
|
|
1279
|
+
...sessionRotateUpdate(input),
|
|
1280
|
+
...(input.claims === undefined
|
|
1281
|
+
? {}
|
|
1282
|
+
: { metadata: encodeSessionMetadata({ ...row, claims: input.claims }) }),
|
|
1283
|
+
});
|
|
971
1284
|
const sessionRevokeUpdate = (input) => ({
|
|
972
1285
|
revoked_at: Number(input.revokedAt),
|
|
973
1286
|
});
|
|
@@ -1032,6 +1345,7 @@ const totpFactorInsert = (row) => ({
|
|
|
1032
1345
|
created_at: Number(row.createdAt),
|
|
1033
1346
|
confirmed_at: row.confirmedAt === undefined ? null : Number(row.confirmedAt),
|
|
1034
1347
|
last_used_at: row.lastUsedAt === undefined ? null : Number(row.lastUsedAt),
|
|
1348
|
+
last_accepted_counter: row.lastAcceptedCounter ?? null,
|
|
1035
1349
|
revoked_at: row.revokedAt === undefined ? null : Number(row.revokedAt),
|
|
1036
1350
|
metadata: jsonEncode(row.metadata),
|
|
1037
1351
|
});
|
|
@@ -1039,8 +1353,9 @@ const totpFactorConfirm = (input) => omitUndefined({
|
|
|
1039
1353
|
confirmed_at: Number(input.confirmedAt),
|
|
1040
1354
|
metadata: input.metadata === undefined ? undefined : jsonEncode(input.metadata),
|
|
1041
1355
|
});
|
|
1042
|
-
const
|
|
1356
|
+
const totpFactorAcceptCounter = (input) => omitUndefined({
|
|
1043
1357
|
last_used_at: Number(input.lastUsedAt),
|
|
1358
|
+
last_accepted_counter: input.counter,
|
|
1044
1359
|
metadata: input.metadata === undefined ? undefined : jsonEncode(input.metadata),
|
|
1045
1360
|
});
|
|
1046
1361
|
const totpFactorRevoke = (input, row) => ({
|
|
@@ -1060,6 +1375,14 @@ const recoveryCodeMarkUsed = (input) => omitUndefined({
|
|
|
1060
1375
|
used_at: Number(input.usedAt),
|
|
1061
1376
|
metadata: input.metadata === undefined ? undefined : jsonEncode(input.metadata),
|
|
1062
1377
|
});
|
|
1378
|
+
const recoveryCodeReplaceActive = (input, metadata) => ({
|
|
1379
|
+
revoked_at: Number(input.revokedAt),
|
|
1380
|
+
...(input.revokeReason === undefined
|
|
1381
|
+
? {}
|
|
1382
|
+
: {
|
|
1383
|
+
metadata: Query.call("json_set", Query.coalesce(metadata, "{}"), "$.revokeReason", input.revokeReason),
|
|
1384
|
+
}),
|
|
1385
|
+
});
|
|
1063
1386
|
const recoveryCodeRevoke = (input, row) => ({
|
|
1064
1387
|
revoked_at: Number(input.revokedAt),
|
|
1065
1388
|
metadata: jsonEncode(metadataWithReason(row.metadata, "revokeReason", input.reason)),
|
|
@@ -1145,7 +1468,9 @@ const oauthProviderTokenVaultValues = (row) => ({
|
|
|
1145
1468
|
refresh_token_ciphertext: row.refreshTokenCiphertext === undefined
|
|
1146
1469
|
? null
|
|
1147
1470
|
: Redacted.value(row.refreshTokenCiphertext),
|
|
1148
|
-
id_token_ciphertext: row.idTokenCiphertext === undefined
|
|
1471
|
+
id_token_ciphertext: row.idTokenCiphertext === undefined
|
|
1472
|
+
? null
|
|
1473
|
+
: Redacted.value(row.idTokenCiphertext),
|
|
1149
1474
|
token_type: row.tokenType,
|
|
1150
1475
|
scopes: jsonEncode(row.scopes),
|
|
1151
1476
|
expires_at: row.expiresAt === undefined ? null : Number(row.expiresAt),
|
|
@@ -1321,6 +1646,101 @@ const webhookReplayUpdate = (input) => ({
|
|
|
1321
1646
|
expires_at: Number(input.expiresAt),
|
|
1322
1647
|
created_at: Number(input.now),
|
|
1323
1648
|
});
|
|
1649
|
+
const permissionScopeValues = (scope) => ({
|
|
1650
|
+
scope_type: scope?.type ?? "global",
|
|
1651
|
+
scope_id_present: scope?.id === undefined ? 0 : 1,
|
|
1652
|
+
scope_id: scope?.id ?? "",
|
|
1653
|
+
});
|
|
1654
|
+
const permissionGrantValues = (input) => ({
|
|
1655
|
+
subject_type: input.subject.type,
|
|
1656
|
+
subject_id: input.subject.id,
|
|
1657
|
+
permission_id: input.permission,
|
|
1658
|
+
...permissionScopeValues(input.scope),
|
|
1659
|
+
expires_at: input.expiresAt === undefined ? null : Number(input.expiresAt),
|
|
1660
|
+
metadata: jsonEncode(input.metadata),
|
|
1661
|
+
revoked_at: null,
|
|
1662
|
+
});
|
|
1663
|
+
const permissionGrantUpdate = (input) => ({
|
|
1664
|
+
expires_at: input.expiresAt === undefined ? null : Number(input.expiresAt),
|
|
1665
|
+
metadata: jsonEncode(input.metadata),
|
|
1666
|
+
revoked_at: null,
|
|
1667
|
+
});
|
|
1668
|
+
const permissionGrantRevoke = (_input, revokedAt) => ({ revoked_at: Number(revokedAt) });
|
|
1669
|
+
const roleGrantValues = (input) => ({
|
|
1670
|
+
subject_type: input.subject.type,
|
|
1671
|
+
subject_id: input.subject.id,
|
|
1672
|
+
role_id: input.role,
|
|
1673
|
+
...permissionScopeValues(input.scope),
|
|
1674
|
+
expires_at: input.expiresAt === undefined ? null : Number(input.expiresAt),
|
|
1675
|
+
metadata: jsonEncode(input.metadata),
|
|
1676
|
+
revoked_at: null,
|
|
1677
|
+
});
|
|
1678
|
+
const roleGrantUpdate = (input) => ({
|
|
1679
|
+
expires_at: input.expiresAt === undefined ? null : Number(input.expiresAt),
|
|
1680
|
+
metadata: jsonEncode(input.metadata),
|
|
1681
|
+
revoked_at: null,
|
|
1682
|
+
});
|
|
1683
|
+
const roleGrantRevoke = (_input, revokedAt) => ({
|
|
1684
|
+
revoked_at: Number(revokedAt),
|
|
1685
|
+
});
|
|
1686
|
+
const rolePermissionValues = (input) => ({
|
|
1687
|
+
role_id: input.role,
|
|
1688
|
+
permission_id: input.permission,
|
|
1689
|
+
scope_type_present: input.scopeType === undefined ? 0 : 1,
|
|
1690
|
+
scope_type: input.scopeType ?? "",
|
|
1691
|
+
});
|
|
1692
|
+
const permissionDefinitionValues = (definition) => ({
|
|
1693
|
+
id: definition.id,
|
|
1694
|
+
description: definition.description ?? null,
|
|
1695
|
+
scope_type_present: definition.scopeType === undefined ? 0 : 1,
|
|
1696
|
+
scope_type: definition.scopeType ?? "",
|
|
1697
|
+
created_at: Number(definition.createdAt),
|
|
1698
|
+
updated_at: Number(definition.updatedAt),
|
|
1699
|
+
disabled_at: definition.disabledAt === undefined ? null : Number(definition.disabledAt),
|
|
1700
|
+
deleted_at: definition.deletedAt === undefined ? null : Number(definition.deletedAt),
|
|
1701
|
+
});
|
|
1702
|
+
const permissionDefinitionUpdate = (input) => ({
|
|
1703
|
+
...(input.description === undefined
|
|
1704
|
+
? {}
|
|
1705
|
+
: { description: input.description }),
|
|
1706
|
+
...(input.scopeType === undefined
|
|
1707
|
+
? {}
|
|
1708
|
+
: {
|
|
1709
|
+
scope_type_present: input.scopeType === null ? 0 : 1,
|
|
1710
|
+
scope_type: input.scopeType ?? "",
|
|
1711
|
+
}),
|
|
1712
|
+
updated_at: Number(input.updatedAt),
|
|
1713
|
+
});
|
|
1714
|
+
const permissionDefinitionSetDisabled = (input) => ({
|
|
1715
|
+
disabled_at: input.disabledAt === undefined ? null : Number(input.disabledAt),
|
|
1716
|
+
updated_at: Number(input.updatedAt),
|
|
1717
|
+
});
|
|
1718
|
+
const permissionDefinitionDelete = (input) => ({
|
|
1719
|
+
deleted_at: Number(input.deletedAt),
|
|
1720
|
+
updated_at: Number(input.updatedAt),
|
|
1721
|
+
});
|
|
1722
|
+
const roleDefinitionValues = (definition) => ({
|
|
1723
|
+
id: definition.id,
|
|
1724
|
+
description: definition.description ?? null,
|
|
1725
|
+
created_at: Number(definition.createdAt),
|
|
1726
|
+
updated_at: Number(definition.updatedAt),
|
|
1727
|
+
disabled_at: definition.disabledAt === undefined ? null : Number(definition.disabledAt),
|
|
1728
|
+
deleted_at: definition.deletedAt === undefined ? null : Number(definition.deletedAt),
|
|
1729
|
+
});
|
|
1730
|
+
const roleDefinitionUpdate = (input) => ({
|
|
1731
|
+
...(input.description === undefined
|
|
1732
|
+
? {}
|
|
1733
|
+
: { description: input.description }),
|
|
1734
|
+
updated_at: Number(input.updatedAt),
|
|
1735
|
+
});
|
|
1736
|
+
const roleDefinitionSetDisabled = (input) => ({
|
|
1737
|
+
disabled_at: input.disabledAt === undefined ? null : Number(input.disabledAt),
|
|
1738
|
+
updated_at: Number(input.updatedAt),
|
|
1739
|
+
});
|
|
1740
|
+
const roleDefinitionDelete = (input) => ({
|
|
1741
|
+
deleted_at: Number(input.deletedAt),
|
|
1742
|
+
updated_at: Number(input.updatedAt),
|
|
1743
|
+
});
|
|
1324
1744
|
const jwtRevocationUpdate = (record) => ({
|
|
1325
1745
|
revoked_at: Number(record.revokedAt),
|
|
1326
1746
|
expires_at: record.expiresAt === undefined ? null : Number(record.expiresAt),
|
|
@@ -1329,6 +1749,7 @@ const jwtRevocationUpdate = (record) => ({
|
|
|
1329
1749
|
const authSqliteTableName = (name, defaultName) => name ?? defaultName;
|
|
1330
1750
|
const makeInternalAuthSqliteTables = (names = {}) => {
|
|
1331
1751
|
const user = makeAuthUserTable(names.user ?? "auth_user");
|
|
1752
|
+
const identity = makeAuthUserIdentityTable(names.identity ?? "auth_user_identity");
|
|
1332
1753
|
const credential = makeAuthCredentialTable(names.credential ?? "auth_credential");
|
|
1333
1754
|
const session = makeAuthSessionTable(names.session ?? "auth_session");
|
|
1334
1755
|
const verification = makeAuthVerificationTable(names.verification ?? "auth_verification");
|
|
@@ -1353,20 +1774,46 @@ const makeInternalAuthSqliteTables = (names = {}) => {
|
|
|
1353
1774
|
const securityTimeline = makeAuthSecurityTimelineTable(names.securityTimeline ?? "auth_security_timeline");
|
|
1354
1775
|
const webhookOutbox = makeAuthWebhookOutboxTable(names.webhookOutbox ?? "auth_webhook_outbox");
|
|
1355
1776
|
const webhookReplay = makeAuthWebhookReplayTable(names.webhookReplay ?? "auth_webhook_replay");
|
|
1777
|
+
const permissionDefinition = makeAuthPermissionDefinitionTable(names.permissionDefinition ?? "auth_permission_definition");
|
|
1778
|
+
const roleDefinition = makeAuthRoleDefinitionTable(names.roleDefinition ?? "auth_role_definition");
|
|
1779
|
+
const permissionGrant = makeAuthPermissionGrantTable(names.permissionGrant ?? "auth_permission_grant");
|
|
1780
|
+
const roleGrant = makeAuthRoleGrantTable(names.roleGrant ?? "auth_role_grant");
|
|
1781
|
+
const rolePermission = makeAuthRolePermissionTable(names.rolePermission ?? "auth_role_permission");
|
|
1356
1782
|
return {
|
|
1357
1783
|
user: {
|
|
1358
1784
|
table: user,
|
|
1359
1785
|
select: {
|
|
1360
1786
|
id: user.id,
|
|
1361
|
-
email: user.email,
|
|
1362
|
-
emailVerified: user.email_verified,
|
|
1363
1787
|
createdAt: user.created_at,
|
|
1364
1788
|
updatedAt: user.updated_at,
|
|
1365
1789
|
disabledAt: user.disabled_at,
|
|
1366
1790
|
metadata: user.metadata,
|
|
1367
1791
|
},
|
|
1368
1792
|
insert: userInsert,
|
|
1369
|
-
|
|
1793
|
+
},
|
|
1794
|
+
identity: {
|
|
1795
|
+
table: identity,
|
|
1796
|
+
select: {
|
|
1797
|
+
id: identity.id,
|
|
1798
|
+
userId: identity.user_id,
|
|
1799
|
+
scopeType: identity.scope_type,
|
|
1800
|
+
scopeId: identity.scope_id,
|
|
1801
|
+
kind: identity.kind,
|
|
1802
|
+
value: identity.value,
|
|
1803
|
+
normalizedValue: identity.normalized_value,
|
|
1804
|
+
verifiedAt: identity.verified_at,
|
|
1805
|
+
isPrimaryLogin: identity.is_primary_login,
|
|
1806
|
+
createdAt: identity.created_at,
|
|
1807
|
+
updatedAt: identity.updated_at,
|
|
1808
|
+
revokedAt: identity.revoked_at,
|
|
1809
|
+
replacedById: identity.replaced_by_id,
|
|
1810
|
+
metadata: identity.metadata,
|
|
1811
|
+
},
|
|
1812
|
+
insert: userIdentityInsert,
|
|
1813
|
+
verify: userIdentityVerify,
|
|
1814
|
+
setPrimary: userIdentitySetPrimary,
|
|
1815
|
+
replace: userIdentityReplace,
|
|
1816
|
+
revoke: userIdentityRevoke,
|
|
1370
1817
|
},
|
|
1371
1818
|
credential: {
|
|
1372
1819
|
table: credential,
|
|
@@ -1392,6 +1839,7 @@ const makeInternalAuthSqliteTables = (names = {}) => {
|
|
|
1392
1839
|
createdAt: session.created_at,
|
|
1393
1840
|
expiresAt: session.expires_at,
|
|
1394
1841
|
authTime: session.auth_time,
|
|
1842
|
+
authenticationEvents: session.authentication_events,
|
|
1395
1843
|
aal: session.aal,
|
|
1396
1844
|
amr: session.amr,
|
|
1397
1845
|
mfaVerifiedAt: session.mfa_verified_at,
|
|
@@ -1404,6 +1852,8 @@ const makeInternalAuthSqliteTables = (names = {}) => {
|
|
|
1404
1852
|
refresh: sessionRefreshUpdate,
|
|
1405
1853
|
rotate: sessionRotateUpdate,
|
|
1406
1854
|
updateClaims: sessionUpdateClaimsUpdate,
|
|
1855
|
+
appendEvidence: sessionAppendEvidenceUpdate,
|
|
1856
|
+
assureAndRotate: sessionAssureAndRotateUpdate,
|
|
1407
1857
|
revoke: sessionRevokeUpdate,
|
|
1408
1858
|
},
|
|
1409
1859
|
verification: {
|
|
@@ -1424,6 +1874,7 @@ const makeInternalAuthSqliteTables = (names = {}) => {
|
|
|
1424
1874
|
auditLog: {
|
|
1425
1875
|
table: auditLog,
|
|
1426
1876
|
select: {
|
|
1877
|
+
storageOrder: auditLog.storage_id,
|
|
1427
1878
|
id: auditLog.id,
|
|
1428
1879
|
type: auditLog.type,
|
|
1429
1880
|
userId: auditLog.user_id,
|
|
@@ -1526,12 +1977,13 @@ const makeInternalAuthSqliteTables = (names = {}) => {
|
|
|
1526
1977
|
createdAt: totpFactor.created_at,
|
|
1527
1978
|
confirmedAt: totpFactor.confirmed_at,
|
|
1528
1979
|
lastUsedAt: totpFactor.last_used_at,
|
|
1980
|
+
lastAcceptedCounter: totpFactor.last_accepted_counter,
|
|
1529
1981
|
revokedAt: totpFactor.revoked_at,
|
|
1530
1982
|
metadata: totpFactor.metadata,
|
|
1531
1983
|
},
|
|
1532
1984
|
insert: totpFactorInsert,
|
|
1533
1985
|
confirm: totpFactorConfirm,
|
|
1534
|
-
|
|
1986
|
+
acceptCounter: totpFactorAcceptCounter,
|
|
1535
1987
|
revoke: totpFactorRevoke,
|
|
1536
1988
|
},
|
|
1537
1989
|
recoveryCode: {
|
|
@@ -1547,6 +1999,7 @@ const makeInternalAuthSqliteTables = (names = {}) => {
|
|
|
1547
1999
|
},
|
|
1548
2000
|
insert: recoveryCodeInsert,
|
|
1549
2001
|
markUsed: recoveryCodeMarkUsed,
|
|
2002
|
+
replaceActive: recoveryCodeReplaceActive,
|
|
1550
2003
|
revoke: recoveryCodeRevoke,
|
|
1551
2004
|
},
|
|
1552
2005
|
apiKey: {
|
|
@@ -1799,6 +2252,84 @@ const makeInternalAuthSqliteTables = (names = {}) => {
|
|
|
1799
2252
|
insert: webhookReplayInsert,
|
|
1800
2253
|
update: webhookReplayUpdate,
|
|
1801
2254
|
},
|
|
2255
|
+
permissionDefinition: {
|
|
2256
|
+
table: permissionDefinition,
|
|
2257
|
+
conflictTarget: "id",
|
|
2258
|
+
select: {
|
|
2259
|
+
id: permissionDefinition.id,
|
|
2260
|
+
description: permissionDefinition.description,
|
|
2261
|
+
scopeTypePresent: permissionDefinition.scope_type_present,
|
|
2262
|
+
scopeType: permissionDefinition.scope_type,
|
|
2263
|
+
createdAt: permissionDefinition.created_at,
|
|
2264
|
+
updatedAt: permissionDefinition.updated_at,
|
|
2265
|
+
disabledAt: permissionDefinition.disabled_at,
|
|
2266
|
+
deletedAt: permissionDefinition.deleted_at,
|
|
2267
|
+
},
|
|
2268
|
+
insert: permissionDefinitionValues,
|
|
2269
|
+
update: permissionDefinitionUpdate,
|
|
2270
|
+
setDisabled: permissionDefinitionSetDisabled,
|
|
2271
|
+
delete: permissionDefinitionDelete,
|
|
2272
|
+
},
|
|
2273
|
+
roleDefinition: {
|
|
2274
|
+
table: roleDefinition,
|
|
2275
|
+
conflictTarget: "id",
|
|
2276
|
+
select: {
|
|
2277
|
+
id: roleDefinition.id,
|
|
2278
|
+
description: roleDefinition.description,
|
|
2279
|
+
createdAt: roleDefinition.created_at,
|
|
2280
|
+
updatedAt: roleDefinition.updated_at,
|
|
2281
|
+
disabledAt: roleDefinition.disabled_at,
|
|
2282
|
+
deletedAt: roleDefinition.deleted_at,
|
|
2283
|
+
},
|
|
2284
|
+
insert: roleDefinitionValues,
|
|
2285
|
+
update: roleDefinitionUpdate,
|
|
2286
|
+
setDisabled: roleDefinitionSetDisabled,
|
|
2287
|
+
delete: roleDefinitionDelete,
|
|
2288
|
+
},
|
|
2289
|
+
permissionGrant: {
|
|
2290
|
+
table: permissionGrant,
|
|
2291
|
+
select: {
|
|
2292
|
+
subjectType: permissionGrant.subject_type,
|
|
2293
|
+
subjectId: permissionGrant.subject_id,
|
|
2294
|
+
permissionId: permissionGrant.permission_id,
|
|
2295
|
+
scopeType: permissionGrant.scope_type,
|
|
2296
|
+
scopeIdPresent: permissionGrant.scope_id_present,
|
|
2297
|
+
scopeId: permissionGrant.scope_id,
|
|
2298
|
+
expiresAt: permissionGrant.expires_at,
|
|
2299
|
+
metadata: permissionGrant.metadata,
|
|
2300
|
+
revokedAt: permissionGrant.revoked_at,
|
|
2301
|
+
},
|
|
2302
|
+
insert: permissionGrantValues,
|
|
2303
|
+
update: permissionGrantUpdate,
|
|
2304
|
+
revoke: permissionGrantRevoke,
|
|
2305
|
+
},
|
|
2306
|
+
roleGrant: {
|
|
2307
|
+
table: roleGrant,
|
|
2308
|
+
select: {
|
|
2309
|
+
subjectType: roleGrant.subject_type,
|
|
2310
|
+
subjectId: roleGrant.subject_id,
|
|
2311
|
+
roleId: roleGrant.role_id,
|
|
2312
|
+
scopeType: roleGrant.scope_type,
|
|
2313
|
+
scopeIdPresent: roleGrant.scope_id_present,
|
|
2314
|
+
scopeId: roleGrant.scope_id,
|
|
2315
|
+
expiresAt: roleGrant.expires_at,
|
|
2316
|
+
metadata: roleGrant.metadata,
|
|
2317
|
+
revokedAt: roleGrant.revoked_at,
|
|
2318
|
+
},
|
|
2319
|
+
insert: roleGrantValues,
|
|
2320
|
+
update: roleGrantUpdate,
|
|
2321
|
+
revoke: roleGrantRevoke,
|
|
2322
|
+
},
|
|
2323
|
+
rolePermission: {
|
|
2324
|
+
table: rolePermission,
|
|
2325
|
+
select: {
|
|
2326
|
+
roleId: rolePermission.role_id,
|
|
2327
|
+
permissionId: rolePermission.permission_id,
|
|
2328
|
+
scopeTypePresent: rolePermission.scope_type_present,
|
|
2329
|
+
scopeType: rolePermission.scope_type,
|
|
2330
|
+
},
|
|
2331
|
+
insert: rolePermissionValues,
|
|
2332
|
+
},
|
|
1802
2333
|
};
|
|
1803
2334
|
};
|
|
1804
2335
|
const asAuthSqliteTables = (tables) => tables;
|
|
@@ -1806,6 +2337,7 @@ export const defineEffectQbAuthTables = (tables) => asAuthSqliteTables(tables);
|
|
|
1806
2337
|
export const makeAuthSqliteTables = (names = {}) => asAuthSqliteTables(makeInternalAuthSqliteTables(names));
|
|
1807
2338
|
export const authSqliteTables = makeAuthSqliteTables();
|
|
1808
2339
|
const selectUserColumns = (tables) => tables.user.select;
|
|
2340
|
+
const selectIdentityColumns = (tables) => tables.identity.select;
|
|
1809
2341
|
const selectCredentialColumns = (tables) => tables.credential.select;
|
|
1810
2342
|
const selectSessionColumns = (tables) => tables.session.select;
|
|
1811
2343
|
const selectVerificationColumns = (tables) => tables.verification.select;
|
|
@@ -1830,7 +2362,16 @@ const selectDomainVerificationColumns = (tables) => tables.domainVerification.se
|
|
|
1830
2362
|
const selectSecurityTimelineColumns = (tables) => tables.securityTimeline.select;
|
|
1831
2363
|
const selectWebhookOutboxColumns = (tables) => tables.webhookOutbox.select;
|
|
1832
2364
|
const selectWebhookReplayColumns = (tables) => tables.webhookReplay.select;
|
|
2365
|
+
const selectPermissionDefinitionColumns = (tables) => tables.permissionDefinition.select;
|
|
2366
|
+
const selectRoleDefinitionColumns = (tables) => tables.roleDefinition.select;
|
|
2367
|
+
const selectPermissionGrantColumns = (tables) => tables.permissionGrant.select;
|
|
2368
|
+
const selectRoleGrantColumns = (tables) => tables.roleGrant.select;
|
|
2369
|
+
const selectRolePermissionColumns = (tables) => tables.rolePermission.select;
|
|
1833
2370
|
const storageError = (entity, operation, cause) => StorageError.fromUnknown(entity, operation, cause);
|
|
2371
|
+
const decodeSessionResult = (operation, decode) => Effect.try({
|
|
2372
|
+
try: decode,
|
|
2373
|
+
catch: (cause) => storageError("session", operation, cause),
|
|
2374
|
+
});
|
|
1834
2375
|
const auditLogStoreError = (operation, cause) => new AuditLogStoreError({
|
|
1835
2376
|
operation,
|
|
1836
2377
|
message: `Audit log ${operation} failed`,
|
|
@@ -1935,22 +2476,36 @@ const webhookReplayStoreError = (operation, cause) => new WebhookReplayStoreErro
|
|
|
1935
2476
|
message: `Webhook replay ${operation} failed`,
|
|
1936
2477
|
cause,
|
|
1937
2478
|
});
|
|
2479
|
+
const permissionStoreError = (operation, cause) => new PermissionStoreError({
|
|
2480
|
+
operation,
|
|
2481
|
+
message: `Permission store ${operation} failed`,
|
|
2482
|
+
cause,
|
|
2483
|
+
});
|
|
1938
2484
|
const findUserById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectUserColumns(tables)).pipe(Query.from(tables.user.table), Query.where(queryEq(tables.user.select.id, id)))).pipe(Effect.mapError((cause) => storageError("user", operation, cause)), Effect.map((rows) => rows[0] === undefined
|
|
1939
2485
|
? Option.none()
|
|
1940
2486
|
: Option.some(decodeUserRow(tables, rows[0]))));
|
|
1941
|
-
const
|
|
2487
|
+
const findIdentity = (executor, tables, where, operation = "find") => executePlan(executor, where.reduce((query, [column, value]) => query.pipe(Query.where(queryEq(column, value))), Query.select(selectIdentityColumns(tables)).pipe(Query.from(tables.identity.table)))).pipe(Effect.mapError((cause) => storageError("identity", operation, cause)), Effect.map((rows) => rows[0] === undefined
|
|
1942
2488
|
? Option.none()
|
|
1943
|
-
: Option.some(
|
|
2489
|
+
: Option.some(decodeUserIdentityRow(tables, rows[0]))));
|
|
1944
2490
|
const findPasswordCredentialByUserId = (executor, tables, userId, operation = "find") => executePlan(executor, Query.select(selectCredentialColumns(tables)).pipe(Query.from(tables.credential.table), Query.where(queryEq(tables.credential.select.userId, userId)), Query.where(queryEq(tables.credential.select.type, "password")))).pipe(Effect.mapError((cause) => storageError("credential", operation, cause)), Effect.map((rows) => rows[0] === undefined
|
|
1945
2491
|
? Option.none()
|
|
1946
2492
|
: Option.some(decodePasswordCredentialRow(tables, rows[0]))));
|
|
1947
2493
|
const findJwtRevocation = (executor, tables, jwtId, operation = "find") => executePlan(executor, Query.select(selectJwtRevocationColumns(tables)).pipe(Query.from(tables.jwtRevocation.table), Query.where(queryEq(tables.jwtRevocation.select.jwtId, jwtId)))).pipe(Effect.mapError((cause) => jwtRevocationStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
|
|
1948
2494
|
? Option.none()
|
|
1949
2495
|
: Option.some(decodeJwtRevocationRow(tables, rows[0]))));
|
|
1950
|
-
const findSessionById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectSessionColumns(tables)).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, id)))).pipe(Effect.mapError((cause) => storageError("session", operation, cause)), Effect.
|
|
2496
|
+
const findSessionById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectSessionColumns(tables)).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, id)))).pipe(Effect.mapError((cause) => storageError("session", operation, cause)), Effect.flatMap((rows) => decodeSessionResult(operation, () => rows[0] === undefined
|
|
1951
2497
|
? Option.none()
|
|
1952
|
-
: Option.some(decodeSessionRow(tables, rows[0]))));
|
|
1953
|
-
const
|
|
2498
|
+
: Option.some(decodeSessionRow(tables, rows[0])))));
|
|
2499
|
+
const findSessionForMutation = (executor, tables, id, operation) => executePlan(executor, Query.select(selectSessionColumns(tables)).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, id)))).pipe(Effect.mapError((cause) => storageError("session", operation, cause)), Effect.flatMap((rows) => decodeSessionResult(operation, () => {
|
|
2500
|
+
const [selected] = rows;
|
|
2501
|
+
return selected === undefined
|
|
2502
|
+
? Option.none()
|
|
2503
|
+
: Option.some({
|
|
2504
|
+
row: decodeSessionRow(tables, selected),
|
|
2505
|
+
authenticationEvents: selected.authenticationEvents,
|
|
2506
|
+
});
|
|
2507
|
+
})));
|
|
2508
|
+
const listSessionsByUser = (executor, tables, input, operation = "listByUser") => executePlan(executor, Query.select(selectSessionColumns(tables)).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.userId, input.userId)))).pipe(Effect.mapError((cause) => storageError("session", operation, cause)), Effect.flatMap((rows) => decodeSessionResult(operation, () => rows.map((row) => decodeSessionRow(tables, row)))));
|
|
1954
2509
|
const findVerificationById = (executor, tables, id, operation = "find") => executePlan(executor, Query.select(selectVerificationColumns(tables)).pipe(Query.from(tables.verification.table), Query.where(queryEq(tables.verification.select.id, id)))).pipe(Effect.mapError((cause) => storageError("verification", operation, cause)), Effect.map((rows) => rows[0] === undefined
|
|
1955
2510
|
? Option.none()
|
|
1956
2511
|
: Option.some(decodeVerificationRow(tables, rows[0]))));
|
|
@@ -2022,7 +2577,9 @@ const findOAuthClientSecretByClientIdAndPrefix = (executor, tables, input, opera
|
|
|
2022
2577
|
const findOAuthProviderModeTokenByHash = (executor, tables, input, operation = "find") => executePlan(executor, Query.select(selectOAuthProviderModeTokenColumns(tables)).pipe(Query.from(tables.oauthProviderModeToken.table), Query.where(queryEq(tables.oauthProviderModeToken.select.tokenHash, input.tokenHash)))).pipe(Effect.mapError((cause) => oauthProviderModeTokenStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
|
|
2023
2578
|
? Option.none()
|
|
2024
2579
|
: Option.some(decodeOAuthProviderModeTokenRow(tables, rows[0]))));
|
|
2025
|
-
const deleteOAuthProviderModeTokenRowsBefore = (executor, tables, column, before) => executePlan(executor, Query.delete(tables.oauthProviderModeToken.table).pipe(Query.where(queryLt(column, Number(before))), Query.returning({
|
|
2580
|
+
const deleteOAuthProviderModeTokenRowsBefore = (executor, tables, column, before) => executePlan(executor, Query.delete(tables.oauthProviderModeToken.table).pipe(Query.where(queryLt(column, Number(before))), Query.returning({
|
|
2581
|
+
tokenHash: tables.oauthProviderModeToken.select.tokenHash,
|
|
2582
|
+
}))).pipe(Effect.map((rows) => rows.length));
|
|
2026
2583
|
const deleteOAuthProviderModeRetiredTokens = (executor, tables, input) => Effect.gen(function* () {
|
|
2027
2584
|
const expiredDeleted = input.beforeExpiresAt === undefined
|
|
2028
2585
|
? 0
|
|
@@ -2096,7 +2653,14 @@ const listAuditLogRecords = (executor, tables, input = {}) => {
|
|
|
2096
2653
|
const query = input.types === undefined
|
|
2097
2654
|
? byOccurredAt
|
|
2098
2655
|
: byOccurredAt.pipe(Query.where(queryIn(tables.auditLog.select.type, input.types[0], ...input.types.slice(1))));
|
|
2099
|
-
|
|
2656
|
+
let ordered = query.pipe(Query.orderBy(tables.auditLog.select.occurredAt, "desc"), Query.orderBy(Query.coalesce(tables.auditLog.select.id, Query.literal("")), "desc"));
|
|
2657
|
+
if (tables.auditLog.select.storageOrder !== undefined) {
|
|
2658
|
+
ordered = ordered.pipe(Query.orderBy(tables.auditLog.select.storageOrder, "asc"));
|
|
2659
|
+
}
|
|
2660
|
+
return executePlan(executor, ordered).pipe(Effect.mapError((cause) => auditLogStoreError("list", cause)), Effect.flatMap((rows) => Effect.try({
|
|
2661
|
+
try: () => filterAuditLogEvents(rows.map((row) => decodeAuditLogEvent(tables, row)), input),
|
|
2662
|
+
catch: (cause) => auditLogStoreError("list", cause),
|
|
2663
|
+
})));
|
|
2100
2664
|
};
|
|
2101
2665
|
const deleteAuditLogRecordsBefore = (executor, tables, input) => {
|
|
2102
2666
|
const base = Query.delete(tables.auditLog.table).pipe(Query.where(queryLt(tables.auditLog.select.occurredAt, Number(input.beforeOccurredAt))));
|
|
@@ -2132,7 +2696,9 @@ const distanceKm = (first, second) => {
|
|
|
2132
2696
|
Math.cos(firstLatitude) *
|
|
2133
2697
|
Math.cos(secondLatitude) *
|
|
2134
2698
|
Math.sin(longitudeDelta / 2) ** 2;
|
|
2135
|
-
return (2 *
|
|
2699
|
+
return (2 *
|
|
2700
|
+
earthRadiusKm *
|
|
2701
|
+
Math.atan2(Math.sqrt(haversine), Math.sqrt(1 - haversine)));
|
|
2136
2702
|
};
|
|
2137
2703
|
const hasCoordinates = (value) => value.latitude !== undefined &&
|
|
2138
2704
|
Number.isFinite(value.latitude) &&
|
|
@@ -2215,18 +2781,287 @@ const deleteLoginRiskHistoryRecordsBefore = (executor, tables, input) => {
|
|
|
2215
2781
|
};
|
|
2216
2782
|
export const makeEffectQbSqliteUserStore = (executor, options = {}) => {
|
|
2217
2783
|
const tables = options.tables ?? authSqliteTables;
|
|
2218
|
-
return UserStore.
|
|
2784
|
+
return UserStore.of({
|
|
2219
2785
|
findById: (id) => findUserById(executor, tables, id),
|
|
2220
|
-
findByEmail: (email) => findUserByEmail(executor, tables, email),
|
|
2221
2786
|
insert: (row) => executePlan(executor, queryInsert(tables.user.table, mutationValues(tables.user.insert(row)))).pipe(Effect.mapError((cause) => storageError("user", "insert", cause)), Effect.asVoid),
|
|
2222
|
-
|
|
2787
|
+
});
|
|
2788
|
+
};
|
|
2789
|
+
const identityConflict = (row) => new IdentityConflictError({
|
|
2790
|
+
scope: row.scope,
|
|
2791
|
+
kind: row.kind,
|
|
2792
|
+
normalizedValue: row.normalizedValue,
|
|
2793
|
+
});
|
|
2794
|
+
const sqliteConstraintDiscriminator = (cause) => {
|
|
2795
|
+
const pending = [cause];
|
|
2796
|
+
const seen = new Set();
|
|
2797
|
+
while (pending.length > 0) {
|
|
2798
|
+
const current = pending.pop();
|
|
2799
|
+
if (current === undefined || current === null || seen.has(current)) {
|
|
2800
|
+
continue;
|
|
2801
|
+
}
|
|
2802
|
+
seen.add(current);
|
|
2803
|
+
if (typeof current === "string") {
|
|
2804
|
+
if (current === "UniqueViolation" ||
|
|
2805
|
+
/\bUNIQUE constraint failed\b/i.test(current) ||
|
|
2806
|
+
/\bPRIMARY KEY constraint failed\b/i.test(current)) {
|
|
2807
|
+
return true;
|
|
2808
|
+
}
|
|
2809
|
+
continue;
|
|
2810
|
+
}
|
|
2811
|
+
if (typeof current !== "object") {
|
|
2812
|
+
continue;
|
|
2813
|
+
}
|
|
2814
|
+
const record = current;
|
|
2815
|
+
const { code, errno, message, name, _tag } = record;
|
|
2816
|
+
if (_tag === "UniqueViolation" || name === "UniqueViolation") {
|
|
2817
|
+
return true;
|
|
2818
|
+
}
|
|
2819
|
+
if (code === "SQLITE_CONSTRAINT_UNIQUE" ||
|
|
2820
|
+
code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
|
|
2821
|
+
return true;
|
|
2822
|
+
}
|
|
2823
|
+
if (errno === 2067 || errno === 1555) {
|
|
2824
|
+
return true;
|
|
2825
|
+
}
|
|
2826
|
+
if (typeof message === "string" &&
|
|
2827
|
+
(/\bUNIQUE constraint failed\b/i.test(message) ||
|
|
2828
|
+
/\bPRIMARY KEY constraint failed\b/i.test(message))) {
|
|
2829
|
+
return true;
|
|
2830
|
+
}
|
|
2831
|
+
pending.push(record.cause, record.reason, record.error, record.raw);
|
|
2832
|
+
if (Array.isArray(record.failures)) {
|
|
2833
|
+
pending.push(...record.failures);
|
|
2834
|
+
}
|
|
2835
|
+
if (Array.isArray(record.reasons)) {
|
|
2836
|
+
pending.push(...record.reasons);
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
return false;
|
|
2840
|
+
};
|
|
2841
|
+
const isAtomicExecuteConflict = (cause, statement) => cause instanceof AtomicPlanError &&
|
|
2842
|
+
cause.phase === "execute" &&
|
|
2843
|
+
cause.statement === statement &&
|
|
2844
|
+
sqliteConstraintDiscriminator(cause.cause) === true;
|
|
2845
|
+
export const makeEffectQbSqliteIdentityStore = (executor, atomic, options = {}) => {
|
|
2846
|
+
const tables = options.tables ?? authSqliteTables;
|
|
2847
|
+
const byId = (id, operation = "find") => findIdentity(executor, tables, [[tables.identity.select.id, id]], operation);
|
|
2848
|
+
const insert = (row) => executePlan(executor, queryInsert(tables.identity.table, mutationValues(tables.identity.insert(row)))).pipe(Effect.mapError(() => identityConflict(row)), Effect.asVoid);
|
|
2849
|
+
return IdentityStore.of({
|
|
2850
|
+
findById: byId,
|
|
2851
|
+
findByKindAndNormalizedValue: (input) => executePlan(executor, Query.select(selectIdentityColumns(tables)).pipe(Query.from(tables.identity.table), Query.where(queryEq(tables.identity.select.scopeType, input.scope.type)), Query.where(queryEq(tables.identity.select.scopeId, identityScopeId(input.scope))), Query.where(queryEq(tables.identity.select.kind, input.kind)), Query.where(queryEq(tables.identity.select.normalizedValue, input.normalizedValue)), Query.where(queryIsNull(tables.identity.select.revokedAt)))).pipe(Effect.mapError((cause) => storageError("identity", "find", cause)), Effect.map((rows) => rows[0] === undefined
|
|
2223
2852
|
? Option.none()
|
|
2224
|
-
: Option.some(
|
|
2853
|
+
: Option.some(decodeUserIdentityRow(tables, rows[0])))),
|
|
2854
|
+
listByUser: (input) => {
|
|
2855
|
+
let query = Query.select(selectIdentityColumns(tables)).pipe(Query.from(tables.identity.table), Query.where(queryEq(tables.identity.select.userId, input.userId)));
|
|
2856
|
+
if (input.includeRevoked !== true) {
|
|
2857
|
+
query = query.pipe(Query.where(queryIsNull(tables.identity.select.revokedAt)));
|
|
2858
|
+
}
|
|
2859
|
+
return executePlan(executor, query).pipe(Effect.mapError((cause) => storageError("identity", "find", cause)), Effect.map((rows) => rows.map((row) => decodeUserIdentityRow(tables, row))));
|
|
2860
|
+
},
|
|
2861
|
+
insert,
|
|
2862
|
+
markVerified: (input) => executePlan(executor, queryUpdate(tables.identity.table, mutationValues(tables.identity.verify(input))).pipe(Query.where(queryEq(tables.identity.select.id, input.id)), Query.where(queryEq(tables.identity.select.normalizedValue, input.expectedNormalizedValue)), Query.where(queryIsNull(tables.identity.select.revokedAt)), Query.returning(selectIdentityColumns(tables)))).pipe(Effect.mapError((cause) => storageError("identity", "update", cause)), Effect.map((rows) => rows[0]
|
|
2863
|
+
? Option.some(decodeUserIdentityRow(tables, rows[0]))
|
|
2864
|
+
: Option.none())),
|
|
2865
|
+
setPrimaryLogin: (input) => {
|
|
2866
|
+
const candidate = aliasTable(tables.identity.table, "primary_candidate");
|
|
2867
|
+
const candidateColumn = (column) => aliasColumn(candidate, column);
|
|
2868
|
+
const candidateExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(candidate), Query.where(queryEq(candidateColumn(tables.identity.select.id), input.identityId)), Query.where(queryEq(candidateColumn(tables.identity.select.userId), input.userId)), Query.where(queryEq(candidateColumn(tables.identity.select.updatedAt), Number(input.expectedUpdatedAt))), Query.where(queryIsNull(candidateColumn(tables.identity.select.revokedAt))), Query.where(queryOr(queryNeq(candidateColumn(tables.identity.select.kind), "email"), queryIsNotNull(candidateColumn(tables.identity.select.verifiedAt))))));
|
|
2869
|
+
const primaryColumnName = boundColumnName(tables.identity.select.isPrimaryLogin);
|
|
2870
|
+
const values = {
|
|
2871
|
+
...mutationValues(tables.identity.setPrimary(input, false)),
|
|
2872
|
+
[primaryColumnName]: Query.case()
|
|
2873
|
+
.when(queryEq(tables.identity.select.id, input.identityId), targetMutationExpression(tables.identity.table, primaryColumnName, 1))
|
|
2874
|
+
.else(targetMutationExpression(tables.identity.table, primaryColumnName, 0)),
|
|
2875
|
+
};
|
|
2876
|
+
const plan = queryUpdate(tables.identity.table, values).pipe(Query.where(queryEq(tables.identity.select.userId, input.userId)), Query.where(queryIsNull(tables.identity.select.revokedAt)), Query.where(candidateExists), Query.returning(selectIdentityColumns(tables)));
|
|
2877
|
+
return executeAtomicPlans(atomic, [plan]).pipe(Effect.mapError((cause) => storageError("identity", "update", cause)), Effect.map(([rows]) => {
|
|
2878
|
+
const selected = rows.find((row) => row.id === input.identityId);
|
|
2879
|
+
return selected === undefined
|
|
2880
|
+
? Option.none()
|
|
2881
|
+
: Option.some(decodeUserIdentityRow(tables, selected));
|
|
2882
|
+
}));
|
|
2883
|
+
},
|
|
2884
|
+
replace: (input) => byId(input.id, "update").pipe(Effect.flatMap((found) => {
|
|
2885
|
+
if (Option.isNone(found) ||
|
|
2886
|
+
found.value.userId !== input.userId ||
|
|
2887
|
+
input.replacement.userId !== input.userId) {
|
|
2888
|
+
return Effect.succeed(Option.none());
|
|
2889
|
+
}
|
|
2890
|
+
const row = {
|
|
2891
|
+
...input.replacement,
|
|
2892
|
+
isPrimaryLogin: input.replacement.isPrimaryLogin ?? found.value.isPrimaryLogin,
|
|
2893
|
+
};
|
|
2894
|
+
const active = aliasTable(tables.identity.table, "active_identity");
|
|
2895
|
+
const activeColumn = (column) => aliasColumn(active, column);
|
|
2896
|
+
const usableCount = queryScalar(Query.select({
|
|
2897
|
+
count: Query.count(activeColumn(tables.identity.select.id)),
|
|
2898
|
+
}).pipe(Query.from(active), Query.where(queryEq(activeColumn(tables.identity.select.userId), input.userId)), Query.where(queryIsNull(activeColumn(tables.identity.select.revokedAt))), Query.where(queryOr(queryNeq(activeColumn(tables.identity.select.kind), "email"), queryIsNotNull(activeColumn(tables.identity.select.verifiedAt))))));
|
|
2899
|
+
const updatePlanBase = queryUpdate(tables.identity.table, mutationValues(tables.identity.replace(input))).pipe(Query.where(queryEq(tables.identity.select.id, input.id)), Query.where(queryEq(tables.identity.select.userId, input.userId)), Query.where(queryEq(tables.identity.select.updatedAt, Number(input.expectedUpdatedAt))), Query.where(queryIsNull(tables.identity.select.revokedAt)));
|
|
2900
|
+
const updatePlan = (isIdentityLoginEligible(found.value) &&
|
|
2901
|
+
!isIdentityLoginEligible(row)
|
|
2902
|
+
? updatePlanBase.pipe(Query.where(queryGt(usableCount, 1)))
|
|
2903
|
+
: updatePlanBase).pipe(Query.returning({
|
|
2904
|
+
...selectIdentityColumns(tables),
|
|
2905
|
+
revokedAt: queryWithoutNullRefinement(tables.identity.select.revokedAt),
|
|
2906
|
+
replacedById: queryWithoutNullRefinement(tables.identity.select.replacedById),
|
|
2907
|
+
}));
|
|
2908
|
+
const insertPlan = queryInsertWhere(tables.identity.table, mutationValues(tables.identity.insert(row)), queryChangedOne()).pipe(Query.returning(selectIdentityColumns(tables)));
|
|
2909
|
+
return executeAtomicPlans(atomic, [
|
|
2910
|
+
updatePlan,
|
|
2911
|
+
insertPlan,
|
|
2912
|
+
]).pipe(Effect.mapError((cause) => isAtomicExecuteConflict(cause, 1)
|
|
2913
|
+
? identityConflict(row)
|
|
2914
|
+
: storageError("identity", "update", cause)), Effect.map(([updated, inserted]) => inserted[0] === undefined || updated[0] === undefined
|
|
2915
|
+
? Option.none()
|
|
2916
|
+
: Option.some({
|
|
2917
|
+
previous: decodeUserIdentityRow(tables, updated[0]),
|
|
2918
|
+
replacement: decodeUserIdentityRow(tables, inserted[0]),
|
|
2919
|
+
})));
|
|
2920
|
+
})),
|
|
2921
|
+
revoke: (input) => byId(input.id, "update").pipe(Effect.flatMap((found) => {
|
|
2922
|
+
if (Option.isNone(found)) {
|
|
2923
|
+
return Effect.succeed(Option.none());
|
|
2924
|
+
}
|
|
2925
|
+
const active = aliasTable(tables.identity.table, "active_identity");
|
|
2926
|
+
const activeColumn = (column) => aliasColumn(active, column);
|
|
2927
|
+
const usableCount = queryScalar(Query.select({
|
|
2928
|
+
count: Query.count(activeColumn(tables.identity.select.id)),
|
|
2929
|
+
}).pipe(Query.from(active), Query.where(queryEq(activeColumn(tables.identity.select.userId), input.userId)), Query.where(queryIsNull(activeColumn(tables.identity.select.revokedAt))), Query.where(queryOr(queryNeq(activeColumn(tables.identity.select.kind), "email"), queryIsNotNull(activeColumn(tables.identity.select.verifiedAt))))));
|
|
2930
|
+
const revokeValues = mutationValues(tables.identity.revoke(input, found.value, tables.identity.select.metadata));
|
|
2931
|
+
const revokePlan = queryUpdate(tables.identity.table, revokeValues).pipe(Query.where(queryEq(tables.identity.select.id, input.id)), Query.where(queryEq(tables.identity.select.userId, input.userId)), Query.where(queryEq(tables.identity.select.updatedAt, Number(input.expectedUpdatedAt))), Query.where(queryIsNull(tables.identity.select.revokedAt)), Query.where(queryOr(queryAnd(queryEq(tables.identity.select.kind, "email"), queryIsNull(tables.identity.select.verifiedAt)), queryGt(usableCount, 1))), Query.returning({
|
|
2932
|
+
...selectIdentityColumns(tables),
|
|
2933
|
+
revokedAt: queryWithoutNullRefinement(tables.identity.select.revokedAt),
|
|
2934
|
+
metadata: queryWithoutNullRefinement(tables.identity.select.metadata),
|
|
2935
|
+
}));
|
|
2936
|
+
const next = aliasTable(tables.identity.table, "next_identity");
|
|
2937
|
+
const nextColumn = (column) => aliasColumn(next, column);
|
|
2938
|
+
const nextId = queryScalar(Query.select({ id: nextColumn(tables.identity.select.id) }).pipe(Query.from(next), Query.where(queryEq(nextColumn(tables.identity.select.userId), input.userId)), Query.where(queryIsNull(nextColumn(tables.identity.select.revokedAt))), Query.where(queryOr(queryNeq(nextColumn(tables.identity.select.kind), "email"), queryIsNotNull(nextColumn(tables.identity.select.verifiedAt)))), Query.orderBy(nextColumn(tables.identity.select.createdAt), "asc"), Query.orderBy(nextColumn(tables.identity.select.id), "asc"), Query.limit(1)));
|
|
2939
|
+
const primary = aliasTable(tables.identity.table, "existing_primary");
|
|
2940
|
+
const primaryColumn = (column) => aliasColumn(primary, column);
|
|
2941
|
+
const primaryExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(primary), Query.where(queryEq(primaryColumn(tables.identity.select.userId), input.userId)), Query.where(queryIsNull(primaryColumn(tables.identity.select.revokedAt))), Query.where(queryEq(primaryColumn(tables.identity.select.isPrimaryLogin), 1))));
|
|
2942
|
+
const promotePlan = queryUpdate(tables.identity.table, mutationValues(tables.identity.setPrimary({
|
|
2943
|
+
userId: input.userId,
|
|
2944
|
+
identityId: input.id,
|
|
2945
|
+
expectedUpdatedAt: input.expectedUpdatedAt,
|
|
2946
|
+
updatedAt: input.revokedAt,
|
|
2947
|
+
}, true))).pipe(Query.where(queryEq(tables.identity.select.id, nextId)), Query.where(queryNot(primaryExists)), Query.where(queryChangedOne()));
|
|
2948
|
+
return executeAtomicPlans(atomic, [
|
|
2949
|
+
revokePlan,
|
|
2950
|
+
promotePlan,
|
|
2951
|
+
]).pipe(Effect.mapError((cause) => storageError("identity", "update", cause)), Effect.map(([rows]) => rows[0] === undefined
|
|
2952
|
+
? Option.none()
|
|
2953
|
+
: Option.some(decodeUserIdentityRow(tables, rows[0]))));
|
|
2954
|
+
})),
|
|
2955
|
+
});
|
|
2956
|
+
};
|
|
2957
|
+
export const makeEffectQbSqliteRegistrationStore = (executor, atomic, options = {}) => {
|
|
2958
|
+
const tables = options.tables ?? authSqliteTables;
|
|
2959
|
+
const identityStore = makeEffectQbSqliteIdentityStore(executor, atomic, options);
|
|
2960
|
+
const credentialStore = makeEffectQbSqliteCredentialStore(executor, options);
|
|
2961
|
+
const classify = (input, cause) => {
|
|
2962
|
+
const failure = () => Effect.fail(new RegistrationFailureError({
|
|
2963
|
+
message: "Atomic registration failed",
|
|
2964
|
+
cause,
|
|
2965
|
+
}));
|
|
2966
|
+
if (cause instanceof AtomicPlanError && cause.phase !== "execute") {
|
|
2967
|
+
return failure();
|
|
2968
|
+
}
|
|
2969
|
+
const statement = cause instanceof AtomicPlanError ? (cause.statement ?? -1) : -1;
|
|
2970
|
+
if (statement >= 0 && !isAtomicExecuteConflict(cause, statement)) {
|
|
2971
|
+
return failure();
|
|
2972
|
+
}
|
|
2973
|
+
if (statement < 0) {
|
|
2974
|
+
return Effect.gen(function* () {
|
|
2975
|
+
if (Option.isSome(yield* findUserById(executor, options.tables ?? authSqliteTables, input.user.id))) {
|
|
2976
|
+
return yield* new RegistrationConflictError({ reason: "user-id" });
|
|
2977
|
+
}
|
|
2978
|
+
if (Option.isSome(yield* identityStore.findById(input.identity.id))) {
|
|
2979
|
+
return yield* new RegistrationConflictError({
|
|
2980
|
+
reason: "identity-id",
|
|
2981
|
+
});
|
|
2982
|
+
}
|
|
2983
|
+
if (Option.isSome(yield* identityStore.findByKindAndNormalizedValue({
|
|
2984
|
+
scope: input.identity.scope,
|
|
2985
|
+
kind: input.identity.kind,
|
|
2986
|
+
normalizedValue: input.identity.normalizedValue,
|
|
2987
|
+
}))) {
|
|
2988
|
+
return yield* new RegistrationConflictError({
|
|
2989
|
+
reason: "identity-value",
|
|
2990
|
+
});
|
|
2991
|
+
}
|
|
2992
|
+
if (input.passwordCredential !== undefined) {
|
|
2993
|
+
const credential = yield* credentialStore.findPasswordByUserId(input.user.id);
|
|
2994
|
+
if (Option.isSome(credential)) {
|
|
2995
|
+
return yield* new RegistrationConflictError({
|
|
2996
|
+
reason: credential.value.id === input.passwordCredential.id
|
|
2997
|
+
? "credential-id"
|
|
2998
|
+
: "password-credential",
|
|
2999
|
+
});
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
return yield* new RegistrationFailureError({
|
|
3003
|
+
message: "Atomic registration failed",
|
|
3004
|
+
cause,
|
|
3005
|
+
});
|
|
3006
|
+
}).pipe(Effect.catch((error) => error instanceof RegistrationConflictError ||
|
|
3007
|
+
error instanceof RegistrationFailureError
|
|
3008
|
+
? Effect.fail(error)
|
|
3009
|
+
: Effect.fail(new RegistrationFailureError({
|
|
3010
|
+
message: "Registration conflict classification failed",
|
|
3011
|
+
cause: error,
|
|
3012
|
+
}))));
|
|
3013
|
+
}
|
|
3014
|
+
if (statement === 0) {
|
|
3015
|
+
return Effect.fail(new RegistrationConflictError({ reason: "user-id" }));
|
|
3016
|
+
}
|
|
3017
|
+
if (statement === 1) {
|
|
3018
|
+
return identityStore.findById(input.identity.id).pipe(Effect.flatMap((found) => Option.isSome(found)
|
|
3019
|
+
? Effect.fail(new RegistrationConflictError({ reason: "identity-id" }))
|
|
3020
|
+
: Effect.fail(new RegistrationConflictError({ reason: "identity-value" }))), Effect.catch(() => Effect.fail(new RegistrationConflictError({ reason: "identity-value" }))));
|
|
3021
|
+
}
|
|
3022
|
+
if (statement === 2 && input.passwordCredential !== undefined) {
|
|
3023
|
+
return credentialStore.findPasswordByUserId(input.user.id).pipe(Effect.flatMap((found) => Effect.fail(new RegistrationConflictError({
|
|
3024
|
+
reason: Option.isSome(found) &&
|
|
3025
|
+
found.value.id === input.passwordCredential?.id
|
|
3026
|
+
? "credential-id"
|
|
3027
|
+
: "password-credential",
|
|
3028
|
+
}))), Effect.catch(() => Effect.fail(new RegistrationConflictError({ reason: "password-credential" }))));
|
|
3029
|
+
}
|
|
3030
|
+
return failure();
|
|
3031
|
+
};
|
|
3032
|
+
return RegistrationStore.of({
|
|
3033
|
+
create: (input) => {
|
|
3034
|
+
if (input.identity.userId !== input.user.id ||
|
|
3035
|
+
(input.passwordCredential !== undefined &&
|
|
3036
|
+
input.passwordCredential.userId !== input.user.id)) {
|
|
3037
|
+
return Effect.fail(new RegistrationConflictError({ reason: "user-id" }));
|
|
3038
|
+
}
|
|
3039
|
+
const userPlan = queryInsert(tables.user.table, mutationValues(tables.user.insert(input.user)));
|
|
3040
|
+
const identityPlan = queryInsert(tables.identity.table, mutationValues(tables.identity.insert(input.identity)));
|
|
3041
|
+
const plans = input.passwordCredential === undefined
|
|
3042
|
+
? [userPlan, identityPlan]
|
|
3043
|
+
: [
|
|
3044
|
+
userPlan,
|
|
3045
|
+
identityPlan,
|
|
3046
|
+
queryInsert(tables.credential.table, mutationValues(tables.credential.insertPassword(input.passwordCredential))),
|
|
3047
|
+
];
|
|
3048
|
+
return atomic.execute(plans).pipe(Effect.map(() => ({
|
|
3049
|
+
user: input.user,
|
|
3050
|
+
identity: {
|
|
3051
|
+
...input.identity,
|
|
3052
|
+
isPrimaryLogin: input.identity.isPrimaryLogin ?? false,
|
|
3053
|
+
updatedAt: input.identity.createdAt,
|
|
3054
|
+
},
|
|
3055
|
+
...(input.passwordCredential === undefined
|
|
3056
|
+
? {}
|
|
3057
|
+
: { passwordCredential: input.passwordCredential }),
|
|
3058
|
+
})), Effect.catch((error) => classify(input, error)));
|
|
3059
|
+
},
|
|
2225
3060
|
});
|
|
2226
3061
|
};
|
|
2227
3062
|
export const makeEffectQbSqliteCredentialStore = (executor, options = {}) => {
|
|
2228
3063
|
const tables = options.tables ?? authSqliteTables;
|
|
2229
|
-
return CredentialStore.
|
|
3064
|
+
return CredentialStore.of({
|
|
2230
3065
|
findPasswordByUserId: (userId) => findPasswordCredentialByUserId(executor, tables, userId),
|
|
2231
3066
|
insertPassword: (row) => executePlan(executor, queryInsert(tables.credential.table, mutationValues(tables.credential.insertPassword(row)))).pipe(Effect.mapError((cause) => storageError("credential", "insert", cause)), Effect.asVoid),
|
|
2232
3067
|
updatePassword: (input) => executePlan(executor, queryUpdate(tables.credential.table, mutationValues(tables.credential.updatePassword(input))).pipe(Query.where(queryEq(tables.credential.select.userId, input.userId)), Query.where(queryEq(tables.credential.select.type, "password")), Query.where(queryIsNull(tables.credential.select.revokedAt)), Query.returning({ id: tables.credential.select.id }))).pipe(Effect.mapError((cause) => storageError("credential", "updatePassword", cause)), Effect.flatMap((rows) => rows[0] === undefined
|
|
@@ -2236,29 +3071,44 @@ export const makeEffectQbSqliteCredentialStore = (executor, options = {}) => {
|
|
|
2236
3071
|
};
|
|
2237
3072
|
export const makeEffectQbSqliteSessionStore = (executor, options = {}) => {
|
|
2238
3073
|
const tables = options.tables ?? authSqliteTables;
|
|
2239
|
-
return SessionStore.
|
|
3074
|
+
return SessionStore.of({
|
|
2240
3075
|
findById: (id) => findSessionById(executor, tables, id),
|
|
2241
3076
|
listByUser: (input) => listSessionsByUser(executor, tables, input),
|
|
2242
|
-
insert: (row) => executePlan(executor, queryInsert(tables.session.table, mutationValues(tables.session.insert(row))))
|
|
2243
|
-
refresh: (input) =>
|
|
2244
|
-
|
|
2245
|
-
|
|
3077
|
+
insert: (row) => decodeSessionResult("insert", () => parseAuthenticationEvents(row.authenticationEvents)).pipe(Effect.flatMap(() => executePlan(executor, queryInsert(tables.session.table, mutationValues(tables.session.insert(row))))), Effect.mapError((cause) => storageError("session", "insert", cause)), Effect.asVoid),
|
|
3078
|
+
refresh: (input) => findSessionForMutation(executor, tables, input.sessionId, "refresh").pipe(Effect.flatMap((existing) => Option.isNone(existing)
|
|
3079
|
+
? Effect.succeed(Option.none())
|
|
3080
|
+
: executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.refresh(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.authenticationEvents, existing.value.authenticationEvents)), Query.returning(selectSessionColumns(tables)))).pipe(Effect.mapError((cause) => storageError("session", "refresh", cause)), Effect.flatMap((rows) => decodeSessionResult("refresh", () => rows[0] === undefined
|
|
2246
3081
|
? Option.none()
|
|
2247
|
-
: Option.some(decodeSessionRow(tables, rows[0]))))
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
return executePlan(executor, query).pipe(Effect.mapError((cause) => storageError("session", "rotate", cause)), Effect.map((rows) => rows[0] === undefined
|
|
3082
|
+
: Option.some(decodeSessionRow(tables, rows[0]))))))),
|
|
3083
|
+
rotate: (input) => findSessionForMutation(executor, tables, input.sessionId, "rotate").pipe(Effect.flatMap((existing) => Option.isNone(existing)
|
|
3084
|
+
? Effect.succeed(Option.none())
|
|
3085
|
+
: executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.rotate(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.authenticationEvents, existing.value.authenticationEvents)), Query.returning(selectSessionColumns(tables)))).pipe(Effect.mapError((cause) => storageError("session", "rotate", cause)), Effect.flatMap((rows) => decodeSessionResult("rotate", () => rows[0] === undefined
|
|
2252
3086
|
? Option.none()
|
|
2253
|
-
: Option.some(decodeSessionRow(tables, rows[0]))))
|
|
2254
|
-
|
|
2255
|
-
|
|
3087
|
+
: Option.some(decodeSessionRow(tables, rows[0]))))))),
|
|
3088
|
+
updateClaims: (input) => findSessionForMutation(executor, tables, input.sessionId, "updateClaims").pipe(Effect.flatMap((selected) => {
|
|
3089
|
+
if (Option.isNone(selected)) {
|
|
3090
|
+
return Effect.succeed(Option.none());
|
|
3091
|
+
}
|
|
3092
|
+
return executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.updateClaims(selected.value.row, input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.authenticationEvents, selected.value.authenticationEvents)), Query.returning(selectSessionColumns(tables)))).pipe(Effect.mapError((cause) => storageError("session", "updateClaims", cause)), Effect.flatMap((rows) => decodeSessionResult("updateClaims", () => rows[0] === undefined
|
|
3093
|
+
? Option.none()
|
|
3094
|
+
: Option.some(decodeSessionRow(tables, rows[0])))));
|
|
3095
|
+
})),
|
|
3096
|
+
appendEvidence: (input) => decodeSessionResult("appendEvidence", () => parseAuthenticationEvents(input.authenticationEvents)).pipe(Effect.flatMap((authenticationEvents) => findSessionForMutation(executor, tables, input.sessionId, "appendEvidence").pipe(Effect.flatMap((existing) => Option.isNone(existing)
|
|
3097
|
+
? Effect.succeed(Option.none())
|
|
3098
|
+
: executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.appendEvidence({
|
|
3099
|
+
...input,
|
|
3100
|
+
authenticationEvents,
|
|
3101
|
+
}))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.authenticationEvents, existing.value.authenticationEvents)), Query.returning(selectSessionColumns(tables)))).pipe(Effect.mapError((cause) => storageError("session", "appendEvidence", cause)), Effect.flatMap((rows) => decodeSessionResult("appendEvidence", () => rows[0] === undefined
|
|
3102
|
+
? Option.none()
|
|
3103
|
+
: Option.some(decodeSessionRow(tables, rows[0]))))))))),
|
|
3104
|
+
assureAndRotate: (input) => decodeSessionResult("assureAndRotate", () => parseAuthenticationEvents(input.authenticationEvents)).pipe(Effect.flatMap(() => findSessionById(executor, tables, input.sessionId, "assureAndRotate")), Effect.flatMap((rowOption) => {
|
|
2256
3105
|
if (Option.isNone(rowOption)) {
|
|
2257
3106
|
return Effect.succeed(Option.none());
|
|
2258
3107
|
}
|
|
2259
|
-
|
|
3108
|
+
const query = queryUpdate(tables.session.table, mutationValues(tables.session.assureAndRotate(rowOption.value, input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.secretHash, input.expectedSecretHash)), Query.where(queryEq(tables.session.select.authenticationEvents, JSON.stringify(input.expectedAuthenticationEvents))), Query.where(queryIsNull(tables.session.select.revokedAt)), Query.where(queryGt(tables.session.select.expiresAt, Number(input.now))), Query.returning(selectSessionColumns(tables)));
|
|
3109
|
+
return executePlan(executor, query).pipe(Effect.mapError((cause) => storageError("session", "assureAndRotate", cause)), Effect.flatMap((rows) => decodeSessionResult("assureAndRotate", () => rows[0] === undefined
|
|
2260
3110
|
? Option.none()
|
|
2261
|
-
: Option.some(decodeSessionRow(tables, rows[0]))));
|
|
3111
|
+
: Option.some(decodeSessionRow(tables, rows[0])))));
|
|
2262
3112
|
})),
|
|
2263
3113
|
revoke: (input) => executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.revoke(input))).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)))).pipe(Effect.mapError((cause) => storageError("session", "revoke", cause)), Effect.asVoid),
|
|
2264
3114
|
revokeAllForUser: (input) => executePlan(executor, queryUpdate(tables.session.table, mutationValues(tables.session.revoke(input))).pipe(Query.where(queryEq(tables.session.select.userId, input.userId)))).pipe(Effect.mapError((cause) => storageError("session", "revokeAllForUser", cause)), Effect.asVoid),
|
|
@@ -2266,7 +3116,7 @@ export const makeEffectQbSqliteSessionStore = (executor, options = {}) => {
|
|
|
2266
3116
|
};
|
|
2267
3117
|
export const makeEffectQbSqliteVerificationStore = (executor, options = {}) => {
|
|
2268
3118
|
const tables = options.tables ?? authSqliteTables;
|
|
2269
|
-
return VerificationStore.
|
|
3119
|
+
return VerificationStore.of({
|
|
2270
3120
|
findById: (id) => findVerificationById(executor, tables, id),
|
|
2271
3121
|
insert: (row) => executePlan(executor, queryInsert(tables.verification.table, mutationValues(tables.verification.insert(row)))).pipe(Effect.mapError((cause) => storageError("verification", "insert", cause)), Effect.asVoid),
|
|
2272
3122
|
consume: (input) => {
|
|
@@ -2294,10 +3144,13 @@ export const makeEffectQbSqliteVerificationStore = (executor, options = {}) => {
|
|
|
2294
3144
|
};
|
|
2295
3145
|
export const makeEffectQbSqliteAuditLogStore = (executor, options = {}) => {
|
|
2296
3146
|
const tables = options.tables ?? authSqliteTables;
|
|
2297
|
-
return AuditLogStore.
|
|
2298
|
-
|
|
3147
|
+
return AuditLogStore.of({
|
|
3148
|
+
insertMany: (events) => Effect.gen(function* () {
|
|
3149
|
+
if (events.length === 0) {
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
2299
3152
|
const createdAt = yield* currentUnixMillis;
|
|
2300
|
-
yield* executePlan(executor,
|
|
3153
|
+
yield* executePlan(executor, queryInsertMany(tables.auditLog.table, events.map((event) => mutationValues(tables.auditLog.insert(event, createdAt))))).pipe(Effect.mapError((cause) => auditLogStoreError("insertMany", cause)), Effect.asVoid);
|
|
2301
3154
|
}),
|
|
2302
3155
|
list: (input = {}) => listAuditLogRecords(executor, tables, input),
|
|
2303
3156
|
deleteBefore: (input) => deleteAuditLogRecordsBefore(executor, tables, input),
|
|
@@ -2305,7 +3158,7 @@ export const makeEffectQbSqliteAuditLogStore = (executor, options = {}) => {
|
|
|
2305
3158
|
};
|
|
2306
3159
|
export const makeEffectQbSqliteLoginApprovalReviewStore = (executor, options = {}) => {
|
|
2307
3160
|
const tables = options.tables ?? authSqliteTables;
|
|
2308
|
-
return LoginApprovalReviewStore.
|
|
3161
|
+
return LoginApprovalReviewStore.of({
|
|
2309
3162
|
insertPending: (input) => executePlan(executor, queryInsert(tables.loginApprovalReview.table, mutationValues(tables.loginApprovalReview.insertPending(input)))).pipe(Effect.mapError((cause) => loginApprovalReviewStoreError("insert", cause)), Effect.as(input)),
|
|
2310
3163
|
findByApprovalChallengeId: (input) => findLoginApprovalReviewByApprovalChallengeId(executor, tables, input),
|
|
2311
3164
|
approve: (input) => executePlan(executor, queryUpdate(tables.loginApprovalReview.table, mutationValues(tables.loginApprovalReview.approve(input))).pipe(Query.where(queryEq(tables.loginApprovalReview.select.approvalChallengeId, input.approvalChallengeId)), Query.where(queryEq(tables.loginApprovalReview.select.status, "pending")), Query.returning(selectLoginApprovalReviewColumns(tables)))).pipe(Effect.mapError((cause) => loginApprovalReviewStoreError("approve", cause)), Effect.map((rows) => rows[0] === undefined
|
|
@@ -2318,7 +3171,7 @@ export const makeEffectQbSqliteLoginApprovalReviewStore = (executor, options = {
|
|
|
2318
3171
|
};
|
|
2319
3172
|
export const makeEffectQbSqliteLoginRiskHistoryStore = (executor, options = {}) => {
|
|
2320
3173
|
const tables = options.tables ?? authSqliteTables;
|
|
2321
|
-
return LoginRiskHistory.
|
|
3174
|
+
return LoginRiskHistory.of({
|
|
2322
3175
|
lookup: (input) => lookupLoginRiskHistory(executor, tables, input, options.loginRiskHistory),
|
|
2323
3176
|
record: (input) => Effect.gen(function* () {
|
|
2324
3177
|
const createdAt = input.createdAt ?? (yield* currentUnixMillis);
|
|
@@ -2329,15 +3182,21 @@ export const makeEffectQbSqliteLoginRiskHistoryStore = (executor, options = {})
|
|
|
2329
3182
|
};
|
|
2330
3183
|
export const makeEffectQbSqliteTrustedDeviceStore = (executor, options = {}) => {
|
|
2331
3184
|
const tables = options.tables ?? authSqliteTables;
|
|
2332
|
-
|
|
3185
|
+
const updateExisting = (input, existing) => executePlan(executor, queryUpdate(tables.trustedDevice.table, mutationValues(tables.trustedDevice.update(input, existing))).pipe(Query.where(queryEq(tables.trustedDevice.select.userId, input.userId)), Query.where(queryEq(tables.trustedDevice.select.tokenHash, input.tokenHash)), Query.where(queryLte(tables.trustedDevice.select.lastSeenAt, Number(input.now))))).pipe(Effect.mapError((cause) => trustedDeviceStoreError("trust", cause)), Effect.asVoid);
|
|
3186
|
+
const updateAfterInsertConflict = (input, originalError) => findTrustedDeviceIncludingExpired(executor, tables, input).pipe(Effect.flatMap((existing) => Option.isSome(existing)
|
|
3187
|
+
? updateExisting(input, existing.value)
|
|
3188
|
+
: Effect.fail(trustedDeviceStoreError("trust", originalError))));
|
|
3189
|
+
return TrustedDeviceStore.of({
|
|
2333
3190
|
find: (input) => findTrustedDevice(executor, tables, input),
|
|
2334
3191
|
listForUser: (input) => listTrustedDevicesForUser(executor, tables, input),
|
|
2335
3192
|
revokeForUser: (input) => executePlan(executor, Query.delete(tables.trustedDevice.table).pipe(Query.where(queryEq(tables.trustedDevice.select.userId, input.userId)), Query.where(queryEq(tables.trustedDevice.select.tokenHash, input.tokenHash)))).pipe(Effect.mapError((cause) => trustedDeviceStoreError("revoke", cause)), Effect.asVoid),
|
|
2336
3193
|
upsert: (input) => findTrustedDeviceIncludingExpired(executor, tables, input).pipe(Effect.flatMap((existing) => {
|
|
2337
3194
|
if (Option.isNone(existing)) {
|
|
2338
|
-
return executePlan(executor, queryInsert(tables.trustedDevice.table, mutationValues(tables.trustedDevice.insert(input, input.now)))).pipe(Effect.
|
|
3195
|
+
return executePlan(executor, queryInsert(tables.trustedDevice.table, mutationValues(tables.trustedDevice.insert(input, input.now)))).pipe(Effect.catch((error) => sqliteConstraintDiscriminator(error)
|
|
3196
|
+
? updateAfterInsertConflict(input, error)
|
|
3197
|
+
: Effect.fail(trustedDeviceStoreError("trust", error))), Effect.asVoid);
|
|
2339
3198
|
}
|
|
2340
|
-
return
|
|
3199
|
+
return updateExisting(input, existing.value);
|
|
2341
3200
|
}), Effect.flatMap(() => findTrustedDeviceIncludingExpired(executor, tables, input)), Effect.flatMap((stored) => Option.isSome(stored)
|
|
2342
3201
|
? Effect.succeed(stored.value)
|
|
2343
3202
|
: Effect.fail(trustedDeviceStoreError("trust", "missing row")))),
|
|
@@ -2345,7 +3204,7 @@ export const makeEffectQbSqliteTrustedDeviceStore = (executor, options = {}) =>
|
|
|
2345
3204
|
};
|
|
2346
3205
|
export const makeEffectQbSqlitePasskeyCredentialStore = (executor, options = {}) => {
|
|
2347
3206
|
const tables = options.tables ?? authSqliteTables;
|
|
2348
|
-
return PasskeyCredentialStore.
|
|
3207
|
+
return PasskeyCredentialStore.of({
|
|
2349
3208
|
insert: (row) => executePlan(executor, queryInsert(tables.passkeyCredential.table, mutationValues(tables.passkeyCredential.insert(row)))).pipe(Effect.mapError((cause) => passkeyCredentialStoreError("insert", cause)), Effect.asVoid),
|
|
2350
3209
|
findByCredentialId: (credentialId) => findPasskeyCredentialByCredentialId(executor, tables, credentialId),
|
|
2351
3210
|
listByUser: (input) => {
|
|
@@ -2355,7 +3214,7 @@ export const makeEffectQbSqlitePasskeyCredentialStore = (executor, options = {})
|
|
|
2355
3214
|
: base.pipe(Query.where(queryIsNull(tables.passkeyCredential.select.revokedAt)));
|
|
2356
3215
|
return executePlan(executor, query).pipe(Effect.mapError((cause) => passkeyCredentialStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodePasskeyCredentialRow(tables, row))));
|
|
2357
3216
|
},
|
|
2358
|
-
updateSignCount: (input) => executePlan(executor, queryUpdate(tables.passkeyCredential.table, mutationValues(tables.passkeyCredential.updateSignCount(input))).pipe(Query.where(queryEq(tables.passkeyCredential.select.credentialId, input.credentialId)), Query.where(queryIsNull(tables.passkeyCredential.select.revokedAt)), Query.returning(selectPasskeyCredentialColumns(tables)))).pipe(Effect.mapError((cause) => passkeyCredentialStoreError("update-sign-count", cause)), Effect.map((rows) => rows[0] === undefined
|
|
3217
|
+
updateSignCount: (input) => executePlan(executor, queryUpdate(tables.passkeyCredential.table, mutationValues(tables.passkeyCredential.updateSignCount(input))).pipe(Query.where(queryEq(tables.passkeyCredential.select.credentialId, input.credentialId)), Query.where(queryEq(tables.passkeyCredential.select.signCount, input.expectedSignCount)), Query.where(queryIsNull(tables.passkeyCredential.select.revokedAt)), Query.returning(selectPasskeyCredentialColumns(tables)))).pipe(Effect.mapError((cause) => passkeyCredentialStoreError("update-sign-count", cause)), Effect.map((rows) => rows[0] === undefined
|
|
2359
3218
|
? Option.none()
|
|
2360
3219
|
: Option.some(decodePasskeyCredentialRow(tables, rows[0])))),
|
|
2361
3220
|
revoke: (input) => findPasskeyCredentialByCredentialId(executor, tables, input.credentialId, "revoke").pipe(Effect.flatMap((row) => {
|
|
@@ -2368,7 +3227,7 @@ export const makeEffectQbSqlitePasskeyCredentialStore = (executor, options = {})
|
|
|
2368
3227
|
};
|
|
2369
3228
|
export const makeEffectQbSqliteTotpFactorStore = (executor, options = {}) => {
|
|
2370
3229
|
const tables = options.tables ?? authSqliteTables;
|
|
2371
|
-
return TotpFactorStore.
|
|
3230
|
+
return TotpFactorStore.of({
|
|
2372
3231
|
insert: (row) => executePlan(executor, queryInsert(tables.totpFactor.table, mutationValues(tables.totpFactor.insert(row)))).pipe(Effect.mapError((cause) => totpFactorStoreError("insert", cause)), Effect.asVoid),
|
|
2373
3232
|
findById: (id) => findTotpFactorById(executor, tables, id),
|
|
2374
3233
|
listByUser: (input) => {
|
|
@@ -2384,9 +3243,35 @@ export const makeEffectQbSqliteTotpFactorStore = (executor, options = {}) => {
|
|
|
2384
3243
|
confirm: (input) => executePlan(executor, queryUpdate(tables.totpFactor.table, mutationValues(tables.totpFactor.confirm(input))).pipe(Query.where(queryEq(tables.totpFactor.select.id, input.id)), Query.where(queryIsNull(tables.totpFactor.select.revokedAt)), Query.returning(selectTotpFactorColumns(tables)))).pipe(Effect.mapError((cause) => totpFactorStoreError("confirm", cause)), Effect.map((rows) => rows[0] === undefined
|
|
2385
3244
|
? Option.none()
|
|
2386
3245
|
: Option.some(decodeTotpFactorRow(tables, rows[0])))),
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
3246
|
+
acceptCounter: (input) => {
|
|
3247
|
+
const attempt = (initial) => executePlan(executor, queryUpdate(tables.totpFactor.table, mutationValues(tables.totpFactor.acceptCounter(input))).pipe(Query.where(queryEq(tables.totpFactor.select.id, input.id)), Query.where(queryIsNull(tables.totpFactor.select.revokedAt)), Query.where(queryGt(tables.totpFactor.select.confirmedAt, 0)), Query.where(initial
|
|
3248
|
+
? queryIsNull(tables.totpFactor.select.lastAcceptedCounter)
|
|
3249
|
+
: queryLt(tables.totpFactor.select.lastAcceptedCounter, input.counter)), Query.returning({ id: tables.totpFactor.select.id })));
|
|
3250
|
+
return Effect.gen(function* () {
|
|
3251
|
+
let rows = yield* attempt(true).pipe(Effect.mapError((cause) => totpFactorStoreError("accept-counter", cause)));
|
|
3252
|
+
if (rows[0] === undefined) {
|
|
3253
|
+
rows = yield* attempt(false).pipe(Effect.mapError((cause) => totpFactorStoreError("accept-counter", cause)));
|
|
3254
|
+
}
|
|
3255
|
+
if (rows[0] !== undefined) {
|
|
3256
|
+
const factor = yield* findTotpFactorById(executor, tables, input.id, "accept-counter");
|
|
3257
|
+
if (Option.isNone(factor)) {
|
|
3258
|
+
return { _tag: "Unavailable" };
|
|
3259
|
+
}
|
|
3260
|
+
return {
|
|
3261
|
+
_tag: "Accepted",
|
|
3262
|
+
factor: factor.value,
|
|
3263
|
+
};
|
|
3264
|
+
}
|
|
3265
|
+
const factor = yield* findTotpFactorById(executor, tables, input.id, "accept-counter");
|
|
3266
|
+
return Option.isSome(factor) &&
|
|
3267
|
+
factor.value.confirmedAt !== undefined &&
|
|
3268
|
+
factor.value.revokedAt === undefined &&
|
|
3269
|
+
factor.value.lastAcceptedCounter !== undefined &&
|
|
3270
|
+
factor.value.lastAcceptedCounter >= input.counter
|
|
3271
|
+
? { _tag: "Replay" }
|
|
3272
|
+
: { _tag: "Unavailable" };
|
|
3273
|
+
});
|
|
3274
|
+
},
|
|
2390
3275
|
revoke: (input) => findTotpFactorById(executor, tables, input.id, "revoke").pipe(Effect.flatMap((row) => {
|
|
2391
3276
|
if (Option.isNone(row)) {
|
|
2392
3277
|
return Effect.void;
|
|
@@ -2395,9 +3280,9 @@ export const makeEffectQbSqliteTotpFactorStore = (executor, options = {}) => {
|
|
|
2395
3280
|
})),
|
|
2396
3281
|
});
|
|
2397
3282
|
};
|
|
2398
|
-
export const makeEffectQbSqliteRecoveryCodeStore = (executor, options = {}) => {
|
|
3283
|
+
export const makeEffectQbSqliteRecoveryCodeStore = (executor, atomic, options = {}) => {
|
|
2399
3284
|
const tables = options.tables ?? authSqliteTables;
|
|
2400
|
-
return RecoveryCodeStore.
|
|
3285
|
+
return RecoveryCodeStore.of({
|
|
2401
3286
|
insertMany: (rows) => Effect.forEach(rows, (row) => executePlan(executor, queryInsert(tables.recoveryCode.table, mutationValues(tables.recoveryCode.insert(row)))).pipe(Effect.mapError((cause) => recoveryCodeStoreError("insert", cause)), Effect.asVoid), { discard: true }),
|
|
2402
3287
|
findById: (id) => findRecoveryCodeById(executor, tables, id),
|
|
2403
3288
|
listByUser: (input) => {
|
|
@@ -2413,6 +3298,13 @@ export const makeEffectQbSqliteRecoveryCodeStore = (executor, options = {}) => {
|
|
|
2413
3298
|
markUsed: (input) => executePlan(executor, queryUpdate(tables.recoveryCode.table, mutationValues(tables.recoveryCode.markUsed(input))).pipe(Query.where(queryEq(tables.recoveryCode.select.id, input.id)), Query.where(queryIsNull(tables.recoveryCode.select.usedAt)), Query.where(queryIsNull(tables.recoveryCode.select.revokedAt)), Query.returning({ id: tables.recoveryCode.select.id }))).pipe(Effect.mapError((cause) => recoveryCodeStoreError("mark-used", cause)), Effect.flatMap((rows) => rows[0] === undefined
|
|
2414
3299
|
? Effect.succeed(Option.none())
|
|
2415
3300
|
: findRecoveryCodeById(executor, tables, input.id, "mark-used"))),
|
|
3301
|
+
replaceActiveForUser: (input) => {
|
|
3302
|
+
const plans = [
|
|
3303
|
+
queryUpdate(tables.recoveryCode.table, mutationValues(tables.recoveryCode.replaceActive(input, tables.recoveryCode.select.metadata))).pipe(Query.where(queryEq(tables.recoveryCode.select.userId, input.userId)), Query.where(queryIsNull(tables.recoveryCode.select.usedAt)), Query.where(queryIsNull(tables.recoveryCode.select.revokedAt))),
|
|
3304
|
+
...input.rows.map((row) => queryInsert(tables.recoveryCode.table, mutationValues(tables.recoveryCode.insert(row)))),
|
|
3305
|
+
];
|
|
3306
|
+
return atomic.execute(plans).pipe(Effect.mapError((cause) => recoveryCodeStoreError("replace-active", cause)), Effect.asVoid);
|
|
3307
|
+
},
|
|
2416
3308
|
revoke: (input) => findRecoveryCodeById(executor, tables, input.id, "revoke").pipe(Effect.flatMap((row) => {
|
|
2417
3309
|
if (Option.isNone(row)) {
|
|
2418
3310
|
return Effect.void;
|
|
@@ -2421,9 +3313,219 @@ export const makeEffectQbSqliteRecoveryCodeStore = (executor, options = {}) => {
|
|
|
2421
3313
|
})),
|
|
2422
3314
|
});
|
|
2423
3315
|
};
|
|
3316
|
+
export const makeEffectQbSqliteRecoveryCodeSessionRotateStore = (executor, atomic, options = {}) => {
|
|
3317
|
+
const tables = options.tables ?? authSqliteTables;
|
|
3318
|
+
return RecoveryCodeSessionRotateStore.of({
|
|
3319
|
+
consumeAndRotate: (input) => findSessionById(executor, tables, input.sessionId, "assureAndRotate").pipe(Effect.flatMap((current) => {
|
|
3320
|
+
if (Option.isNone(current) || current.value.userId !== input.userId) {
|
|
3321
|
+
return Effect.succeed(Option.none());
|
|
3322
|
+
}
|
|
3323
|
+
try {
|
|
3324
|
+
assertSessionAssurance(input);
|
|
3325
|
+
}
|
|
3326
|
+
catch (error) {
|
|
3327
|
+
return Effect.fail(new RecoveryCodeSessionRotateStoreError({
|
|
3328
|
+
message: "Session assurance validation failed",
|
|
3329
|
+
cause: error,
|
|
3330
|
+
}));
|
|
3331
|
+
}
|
|
3332
|
+
const metadata = input.claims === undefined
|
|
3333
|
+
? undefined
|
|
3334
|
+
: encodeSessionMetadata({
|
|
3335
|
+
...current.value,
|
|
3336
|
+
claims: input.claims,
|
|
3337
|
+
});
|
|
3338
|
+
const sessionExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.userId, input.userId)), Query.where(queryEq(tables.session.select.secretHash, input.expectedSecretHash)), Query.where(queryEq(tables.session.select.authenticationEvents, JSON.stringify(input.expectedAuthenticationEvents))), Query.where(queryIsNull(tables.session.select.revokedAt)), Query.where(queryGt(tables.session.select.expiresAt, Number(input.now)))));
|
|
3339
|
+
const consumePlan = queryUpdate(tables.recoveryCode.table, mutationValues(tables.recoveryCode.markUsed({
|
|
3340
|
+
id: input.codeId,
|
|
3341
|
+
usedAt: input.usedAt,
|
|
3342
|
+
...(input.codeMetadata === undefined
|
|
3343
|
+
? {}
|
|
3344
|
+
: { metadata: input.codeMetadata }),
|
|
3345
|
+
}))).pipe(Query.where(queryEq(tables.recoveryCode.select.id, input.codeId)), Query.where(queryEq(tables.recoveryCode.select.userId, input.userId)), Query.where(queryIsNull(tables.recoveryCode.select.usedAt)), Query.where(queryIsNull(tables.recoveryCode.select.revokedAt)), Query.where(sessionExists));
|
|
3346
|
+
const sessionValues = mutationValues(tables.session.assureAndRotate(current.value, input));
|
|
3347
|
+
if (metadata !== undefined) {
|
|
3348
|
+
sessionValues[boundColumnName(tables.session.select.metadata)] =
|
|
3349
|
+
metadata;
|
|
3350
|
+
}
|
|
3351
|
+
const rotatePlan = queryUpdate(tables.session.table, sessionValues).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.userId, input.userId)), Query.where(queryEq(tables.session.select.secretHash, input.expectedSecretHash)), Query.where(queryEq(tables.session.select.authenticationEvents, JSON.stringify(input.expectedAuthenticationEvents))), Query.where(queryIsNull(tables.session.select.revokedAt)), Query.where(queryGt(tables.session.select.expiresAt, Number(input.now))), Query.where(queryChangedOne()), Query.returning({ id: tables.session.select.id }));
|
|
3352
|
+
return executeAtomicPlans(atomic, [
|
|
3353
|
+
consumePlan,
|
|
3354
|
+
rotatePlan,
|
|
3355
|
+
]).pipe(Effect.mapError((cause) => new RecoveryCodeSessionRotateStoreError({
|
|
3356
|
+
message: "Atomic recovery session rotation failed",
|
|
3357
|
+
cause,
|
|
3358
|
+
})), Effect.flatMap((results) => results[1].length === 0
|
|
3359
|
+
? Effect.succeed(Option.none())
|
|
3360
|
+
: findSessionById(executor, tables, input.sessionId, "assureAndRotate").pipe(Effect.mapError((cause) => new RecoveryCodeSessionRotateStoreError({
|
|
3361
|
+
message: "Failed to load rotated session",
|
|
3362
|
+
cause,
|
|
3363
|
+
})))));
|
|
3364
|
+
}), Effect.mapError((cause) => cause instanceof RecoveryCodeSessionRotateStoreError
|
|
3365
|
+
? cause
|
|
3366
|
+
: new RecoveryCodeSessionRotateStoreError({
|
|
3367
|
+
message: "Failed to load current session",
|
|
3368
|
+
cause,
|
|
3369
|
+
}))),
|
|
3370
|
+
});
|
|
3371
|
+
};
|
|
3372
|
+
export const makeEffectQbSqliteTotpSessionRotateStore = (executor, atomic, options = {}) => {
|
|
3373
|
+
const tables = options.tables ?? authSqliteTables;
|
|
3374
|
+
return TotpSessionRotateStore.of({
|
|
3375
|
+
acceptAndRotate: (input) => findSessionById(executor, tables, input.sessionId, "assureAndRotate").pipe(Effect.flatMap((current) => {
|
|
3376
|
+
if (Option.isNone(current) || current.value.userId !== input.userId) {
|
|
3377
|
+
return Effect.succeed(Option.none());
|
|
3378
|
+
}
|
|
3379
|
+
try {
|
|
3380
|
+
assertSessionAssurance(input);
|
|
3381
|
+
}
|
|
3382
|
+
catch (error) {
|
|
3383
|
+
return Effect.fail(new TotpSessionRotateStoreError({
|
|
3384
|
+
message: "Session assurance validation failed",
|
|
3385
|
+
cause: error,
|
|
3386
|
+
}));
|
|
3387
|
+
}
|
|
3388
|
+
const metadata = input.claims === undefined
|
|
3389
|
+
? undefined
|
|
3390
|
+
: encodeSessionMetadata({
|
|
3391
|
+
...current.value,
|
|
3392
|
+
claims: input.claims,
|
|
3393
|
+
});
|
|
3394
|
+
const sessionExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.userId, input.userId)), Query.where(queryEq(tables.session.select.secretHash, input.expectedSecretHash)), Query.where(queryEq(tables.session.select.authenticationEvents, JSON.stringify(input.expectedAuthenticationEvents))), Query.where(queryIsNull(tables.session.select.revokedAt)), Query.where(queryGt(tables.session.select.expiresAt, Number(input.now)))));
|
|
3395
|
+
const acceptPlan = queryUpdate(tables.totpFactor.table, mutationValues(tables.totpFactor.acceptCounter({
|
|
3396
|
+
id: input.factorId,
|
|
3397
|
+
counter: input.counter,
|
|
3398
|
+
lastUsedAt: input.lastUsedAt,
|
|
3399
|
+
...(input.factorMetadata === undefined
|
|
3400
|
+
? {}
|
|
3401
|
+
: { metadata: input.factorMetadata }),
|
|
3402
|
+
}))).pipe(Query.where(queryEq(tables.totpFactor.select.id, input.factorId)), Query.where(queryEq(tables.totpFactor.select.userId, input.userId)), Query.where(queryIsNotNull(tables.totpFactor.select.confirmedAt)), Query.where(queryIsNull(tables.totpFactor.select.revokedAt)), Query.where(queryOr(queryIsNull(tables.totpFactor.select.lastAcceptedCounter), queryLt(tables.totpFactor.select.lastAcceptedCounter, input.counter))), Query.where(sessionExists));
|
|
3403
|
+
const sessionValues = mutationValues(tables.session.assureAndRotate(current.value, input));
|
|
3404
|
+
if (metadata !== undefined) {
|
|
3405
|
+
sessionValues[boundColumnName(tables.session.select.metadata)] =
|
|
3406
|
+
metadata;
|
|
3407
|
+
}
|
|
3408
|
+
const rotatePlan = queryUpdate(tables.session.table, sessionValues).pipe(Query.where(queryEq(tables.session.select.id, input.sessionId)), Query.where(queryEq(tables.session.select.userId, input.userId)), Query.where(queryEq(tables.session.select.secretHash, input.expectedSecretHash)), Query.where(queryEq(tables.session.select.authenticationEvents, JSON.stringify(input.expectedAuthenticationEvents))), Query.where(queryIsNull(tables.session.select.revokedAt)), Query.where(queryGt(tables.session.select.expiresAt, Number(input.now))), Query.where(queryChangedOne()), Query.returning({ id: tables.session.select.id }));
|
|
3409
|
+
return executeAtomicPlans(atomic, [
|
|
3410
|
+
acceptPlan,
|
|
3411
|
+
rotatePlan,
|
|
3412
|
+
]).pipe(Effect.mapError((cause) => new TotpSessionRotateStoreError({
|
|
3413
|
+
message: "Atomic TOTP session rotation failed",
|
|
3414
|
+
cause,
|
|
3415
|
+
})), Effect.flatMap((results) => results[1].length === 0
|
|
3416
|
+
? Effect.succeed(Option.none())
|
|
3417
|
+
: findSessionById(executor, tables, input.sessionId, "assureAndRotate").pipe(Effect.mapError((cause) => new TotpSessionRotateStoreError({
|
|
3418
|
+
message: "Failed to load rotated session",
|
|
3419
|
+
cause,
|
|
3420
|
+
})))));
|
|
3421
|
+
}), Effect.mapError((cause) => cause instanceof TotpSessionRotateStoreError
|
|
3422
|
+
? cause
|
|
3423
|
+
: new TotpSessionRotateStoreError({
|
|
3424
|
+
message: "Failed to load current session",
|
|
3425
|
+
cause,
|
|
3426
|
+
}))),
|
|
3427
|
+
});
|
|
3428
|
+
};
|
|
3429
|
+
export const makeEffectQbSqliteTotpLoginCommitStore = (atomic, options = {}) => {
|
|
3430
|
+
const tables = options.tables ?? authSqliteTables;
|
|
3431
|
+
return TotpLoginCommitStore.of({
|
|
3432
|
+
commit: (input) => {
|
|
3433
|
+
const { verification, session } = input;
|
|
3434
|
+
if (verification.type !== "auth-flow-state" ||
|
|
3435
|
+
verification.subject !== input.userId ||
|
|
3436
|
+
(session === undefined && input.continuation !== "login-approval") ||
|
|
3437
|
+
(session !== undefined && input.continuation !== undefined) ||
|
|
3438
|
+
(session !== undefined && session.userId !== input.userId)) {
|
|
3439
|
+
return Effect.succeed(false);
|
|
3440
|
+
}
|
|
3441
|
+
const factorExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.totpFactor.table), Query.where(queryEq(tables.totpFactor.select.id, input.factorId)), Query.where(queryEq(tables.totpFactor.select.userId, input.userId)), Query.where(queryIsNotNull(tables.totpFactor.select.confirmedAt)), Query.where(queryIsNull(tables.totpFactor.select.revokedAt)), Query.where(queryOr(queryIsNull(tables.totpFactor.select.lastAcceptedCounter), queryLt(tables.totpFactor.select.lastAcceptedCounter, input.counter)))));
|
|
3442
|
+
const verificationPlan = queryUpdate(tables.verification.table, mutationValues(tables.verification.consume({
|
|
3443
|
+
id: verification.id,
|
|
3444
|
+
type: verification.type,
|
|
3445
|
+
consumedAt: input.consumedAt,
|
|
3446
|
+
}))).pipe(Query.where(queryEq(tables.verification.select.id, verification.id)), Query.where(queryEq(tables.verification.select.type, verification.type)), Query.where(queryEq(tables.verification.select.subject, verification.subject)), Query.where(queryIsNotDistinctFrom(tables.verification.select.secretHash, verification.secretHash ?? null)), Query.where(queryEq(tables.verification.select.createdAt, Number(verification.createdAt))), Query.where(queryEq(tables.verification.select.expiresAt, Number(verification.expiresAt))), Query.where(queryIsNotDistinctFrom(tables.verification.select.metadata, jsonEncode(verification.metadata))), Query.where(queryIsNull(tables.verification.select.consumedAt)), Query.where(queryGt(tables.verification.select.expiresAt, Number(input.consumedAt))), Query.where(factorExists));
|
|
3447
|
+
const factorPlan = queryUpdate(tables.totpFactor.table, mutationValues(tables.totpFactor.acceptCounter({
|
|
3448
|
+
id: input.factorId,
|
|
3449
|
+
counter: input.counter,
|
|
3450
|
+
lastUsedAt: input.lastUsedAt,
|
|
3451
|
+
...(input.factorMetadata === undefined
|
|
3452
|
+
? {}
|
|
3453
|
+
: { metadata: input.factorMetadata }),
|
|
3454
|
+
}))).pipe(Query.where(queryEq(tables.totpFactor.select.id, input.factorId)), Query.where(queryEq(tables.totpFactor.select.userId, input.userId)), Query.where(queryIsNotNull(tables.totpFactor.select.confirmedAt)), Query.where(queryIsNull(tables.totpFactor.select.revokedAt)), Query.where(queryOr(queryIsNull(tables.totpFactor.select.lastAcceptedCounter), queryLt(tables.totpFactor.select.lastAcceptedCounter, input.counter))), Query.where(queryChangedOne()), Query.returning({ id: tables.totpFactor.select.id }));
|
|
3455
|
+
if (session === undefined) {
|
|
3456
|
+
return executeAtomicPlans(atomic, [
|
|
3457
|
+
verificationPlan,
|
|
3458
|
+
factorPlan,
|
|
3459
|
+
]).pipe(Effect.map((results) => results[1].length === 1), Effect.mapError((cause) => new TotpLoginCommitStoreError({
|
|
3460
|
+
message: "Atomic TOTP login commit failed",
|
|
3461
|
+
cause,
|
|
3462
|
+
})));
|
|
3463
|
+
}
|
|
3464
|
+
const sessionExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, session.id))));
|
|
3465
|
+
const sessionVerificationPlan = verificationPlan.pipe(Query.where(queryNot(sessionExists)));
|
|
3466
|
+
const sessionPlan = queryInsertWhere(tables.session.table, mutationValues(tables.session.insert(session)), queryChangedOne()).pipe(Query.returning({ id: tables.session.select.id }));
|
|
3467
|
+
return executeAtomicPlans(atomic, [
|
|
3468
|
+
sessionVerificationPlan,
|
|
3469
|
+
factorPlan,
|
|
3470
|
+
sessionPlan,
|
|
3471
|
+
]).pipe(Effect.map((results) => results[2].length === 1), Effect.mapError((cause) => new TotpLoginCommitStoreError({
|
|
3472
|
+
message: "Atomic TOTP login commit failed",
|
|
3473
|
+
cause,
|
|
3474
|
+
})));
|
|
3475
|
+
},
|
|
3476
|
+
});
|
|
3477
|
+
};
|
|
3478
|
+
export const makeEffectQbSqliteRecoveryCodeLoginCommitStore = (atomic, options = {}) => {
|
|
3479
|
+
const tables = options.tables ?? authSqliteTables;
|
|
3480
|
+
return RecoveryCodeLoginCommitStore.of({
|
|
3481
|
+
commit: (input) => {
|
|
3482
|
+
const { verification, session } = input;
|
|
3483
|
+
if (verification.type !== "auth-flow-state" ||
|
|
3484
|
+
verification.subject !== input.userId ||
|
|
3485
|
+
(session === undefined && input.continuation !== "login-approval") ||
|
|
3486
|
+
(session !== undefined && input.continuation !== undefined) ||
|
|
3487
|
+
(session !== undefined && session.userId !== input.userId)) {
|
|
3488
|
+
return Effect.succeed(false);
|
|
3489
|
+
}
|
|
3490
|
+
const codeExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.recoveryCode.table), Query.where(queryEq(tables.recoveryCode.select.id, input.codeId)), Query.where(queryEq(tables.recoveryCode.select.userId, input.userId)), Query.where(queryIsNull(tables.recoveryCode.select.usedAt)), Query.where(queryIsNull(tables.recoveryCode.select.revokedAt))));
|
|
3491
|
+
const verificationPlan = queryUpdate(tables.verification.table, mutationValues(tables.verification.consume({
|
|
3492
|
+
id: verification.id,
|
|
3493
|
+
type: verification.type,
|
|
3494
|
+
consumedAt: input.consumedAt,
|
|
3495
|
+
}))).pipe(Query.where(queryEq(tables.verification.select.id, verification.id)), Query.where(queryEq(tables.verification.select.type, verification.type)), Query.where(queryEq(tables.verification.select.subject, verification.subject)), Query.where(queryIsNotDistinctFrom(tables.verification.select.secretHash, verification.secretHash ?? null)), Query.where(queryEq(tables.verification.select.createdAt, Number(verification.createdAt))), Query.where(queryEq(tables.verification.select.expiresAt, Number(verification.expiresAt))), Query.where(queryIsNotDistinctFrom(tables.verification.select.metadata, jsonEncode(verification.metadata))), Query.where(queryIsNull(tables.verification.select.consumedAt)), Query.where(queryGt(tables.verification.select.expiresAt, Number(input.consumedAt))), Query.where(codeExists));
|
|
3496
|
+
const codePlan = queryUpdate(tables.recoveryCode.table, mutationValues(tables.recoveryCode.markUsed({
|
|
3497
|
+
id: input.codeId,
|
|
3498
|
+
usedAt: input.usedAt,
|
|
3499
|
+
...(input.codeMetadata === undefined
|
|
3500
|
+
? {}
|
|
3501
|
+
: { metadata: input.codeMetadata }),
|
|
3502
|
+
}))).pipe(Query.where(queryEq(tables.recoveryCode.select.id, input.codeId)), Query.where(queryEq(tables.recoveryCode.select.userId, input.userId)), Query.where(queryIsNull(tables.recoveryCode.select.usedAt)), Query.where(queryIsNull(tables.recoveryCode.select.revokedAt)), Query.where(queryChangedOne()), Query.returning({ id: tables.recoveryCode.select.id }));
|
|
3503
|
+
if (session === undefined) {
|
|
3504
|
+
return executeAtomicPlans(atomic, [
|
|
3505
|
+
verificationPlan,
|
|
3506
|
+
codePlan,
|
|
3507
|
+
]).pipe(Effect.map((results) => results[1].length === 1), Effect.mapError((cause) => new RecoveryCodeLoginCommitStoreError({
|
|
3508
|
+
message: "Atomic recovery-code login commit failed",
|
|
3509
|
+
cause,
|
|
3510
|
+
})));
|
|
3511
|
+
}
|
|
3512
|
+
const sessionExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.session.table), Query.where(queryEq(tables.session.select.id, session.id))));
|
|
3513
|
+
const sessionVerificationPlan = verificationPlan.pipe(Query.where(queryNot(sessionExists)));
|
|
3514
|
+
const sessionPlan = queryInsertWhere(tables.session.table, mutationValues(tables.session.insert(session)), queryChangedOne()).pipe(Query.returning({ id: tables.session.select.id }));
|
|
3515
|
+
return executeAtomicPlans(atomic, [
|
|
3516
|
+
sessionVerificationPlan,
|
|
3517
|
+
codePlan,
|
|
3518
|
+
sessionPlan,
|
|
3519
|
+
]).pipe(Effect.map((results) => results[2].length === 1), Effect.mapError((cause) => new RecoveryCodeLoginCommitStoreError({
|
|
3520
|
+
message: "Atomic recovery-code login commit failed",
|
|
3521
|
+
cause,
|
|
3522
|
+
})));
|
|
3523
|
+
},
|
|
3524
|
+
});
|
|
3525
|
+
};
|
|
2424
3526
|
export const makeEffectQbSqliteApiKeyStore = (executor, options = {}) => {
|
|
2425
3527
|
const tables = options.tables ?? authSqliteTables;
|
|
2426
|
-
return ApiKeyStore.
|
|
3528
|
+
return ApiKeyStore.of({
|
|
2427
3529
|
insert: (row) => executePlan(executor, queryInsert(tables.apiKey.table, mutationValues(tables.apiKey.insert(row)))).pipe(Effect.mapError((cause) => apiKeyStoreError("insert", cause)), Effect.asVoid),
|
|
2428
3530
|
findById: (id) => findApiKeyById(executor, tables, id),
|
|
2429
3531
|
findByPrefix: (prefix) => findApiKeyByPrefix(executor, tables, prefix),
|
|
@@ -2445,9 +3547,9 @@ export const makeEffectQbSqliteApiKeyStore = (executor, options = {}) => {
|
|
|
2445
3547
|
})),
|
|
2446
3548
|
});
|
|
2447
3549
|
};
|
|
2448
|
-
export const makeEffectQbSqliteRefreshTokenStore = (executor, options = {}) => {
|
|
3550
|
+
export const makeEffectQbSqliteRefreshTokenStore = (executor, atomic, options = {}) => {
|
|
2449
3551
|
const tables = options.tables ?? authSqliteTables;
|
|
2450
|
-
return RefreshTokenStore.
|
|
3552
|
+
return RefreshTokenStore.of({
|
|
2451
3553
|
insert: (row) => executePlan(executor, queryInsert(tables.refreshToken.table, mutationValues(tables.refreshToken.insert(row)))).pipe(Effect.mapError((cause) => refreshTokenStoreError("insert", cause)), Effect.asVoid),
|
|
2452
3554
|
findById: (id) => findRefreshTokenById(executor, tables, id),
|
|
2453
3555
|
findBySecretHash: (hash) => findRefreshTokenBySecretHash(executor, tables, hash),
|
|
@@ -2465,21 +3567,28 @@ export const makeEffectQbSqliteRefreshTokenStore = (executor, options = {}) => {
|
|
|
2465
3567
|
: base.pipe(Query.where(queryIsNull(tables.refreshToken.select.revokedAt)));
|
|
2466
3568
|
return executePlan(executor, query).pipe(Effect.mapError((cause) => refreshTokenStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodeRefreshTokenRow(tables, row))));
|
|
2467
3569
|
},
|
|
2468
|
-
rotate: (input) =>
|
|
2469
|
-
const
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
return Effect.succeed(Option.none());
|
|
2476
|
-
}
|
|
2477
|
-
return executePlan(executor, queryInsert(tables.refreshToken.table, mutationValues(tables.refreshToken.insert(input.replacement)))).pipe(Effect.mapError((cause) => refreshTokenStoreError("rotate", cause)), Effect.as(Option.some({
|
|
2478
|
-
previous: previous.value,
|
|
2479
|
-
replacement: input.replacement,
|
|
2480
|
-
})));
|
|
3570
|
+
rotate: (input) => {
|
|
3571
|
+
const { replacement } = input;
|
|
3572
|
+
const rotatePlan = queryUpdate(tables.refreshToken.table, mutationValues(tables.refreshToken.rotate(input))).pipe(Query.where(queryEq(tables.refreshToken.select.id, input.id)), Query.where(queryIsNull(tables.refreshToken.select.rotatedAt)), Query.where(queryIsNull(tables.refreshToken.select.revokedAt)), Query.returning({
|
|
3573
|
+
...selectRefreshTokenColumns(tables),
|
|
3574
|
+
lastUsedAt: queryWithoutNullRefinement(tables.refreshToken.select.lastUsedAt),
|
|
3575
|
+
rotatedAt: queryWithoutNullRefinement(tables.refreshToken.select.rotatedAt),
|
|
3576
|
+
replacedById: queryWithoutNullRefinement(tables.refreshToken.select.replacedById),
|
|
2481
3577
|
}));
|
|
2482
|
-
|
|
3578
|
+
const replacementPlan = queryInsertWhere(tables.refreshToken.table, mutationValues(tables.refreshToken.insert(replacement)), queryChangedOne()).pipe(Query.returning({ id: tables.refreshToken.select.id }));
|
|
3579
|
+
return executeAtomicPlans(atomic, [
|
|
3580
|
+
rotatePlan,
|
|
3581
|
+
replacementPlan,
|
|
3582
|
+
]).pipe(Effect.mapError((cause) => refreshTokenStoreError("rotate", cause)), Effect.map(([previousRows, replacementRows]) => {
|
|
3583
|
+
const [previous] = previousRows;
|
|
3584
|
+
return previous === undefined || replacementRows.length === 0
|
|
3585
|
+
? Option.none()
|
|
3586
|
+
: Option.some({
|
|
3587
|
+
previous: decodeRefreshTokenRow(tables, previous),
|
|
3588
|
+
replacement,
|
|
3589
|
+
});
|
|
3590
|
+
}));
|
|
3591
|
+
},
|
|
2483
3592
|
markReuseDetected: (input) => executePlan(executor, queryUpdate(tables.refreshToken.table, mutationValues(tables.refreshToken.markReuseDetected(input))).pipe(Query.where(queryEq(tables.refreshToken.select.id, input.id)), Query.returning(selectRefreshTokenColumns(tables)))).pipe(Effect.mapError((cause) => refreshTokenStoreError("reuse", cause)), Effect.map((rows) => rows[0] === undefined
|
|
2484
3593
|
? Option.none()
|
|
2485
3594
|
: Option.some(decodeRefreshTokenRow(tables, rows[0])))),
|
|
@@ -2494,7 +3603,7 @@ export const makeEffectQbSqliteRefreshTokenStore = (executor, options = {}) => {
|
|
|
2494
3603
|
};
|
|
2495
3604
|
export const makeEffectQbSqliteJwtRevocationStore = (executor, options = {}) => {
|
|
2496
3605
|
const tables = options.tables ?? authSqliteTables;
|
|
2497
|
-
return JwtRevocationStore.
|
|
3606
|
+
return JwtRevocationStore.of({
|
|
2498
3607
|
revoke: (record) => findJwtRevocation(executor, tables, record.jwtId, "revoke").pipe(Effect.flatMap((existing) => {
|
|
2499
3608
|
if (Option.isNone(existing)) {
|
|
2500
3609
|
return executePlan(executor, queryInsert(tables.jwtRevocation.table, mutationValues(tables.jwtRevocation.insert(record))));
|
|
@@ -2508,7 +3617,7 @@ export const makeEffectQbSqliteJwtRevocationStore = (executor, options = {}) =>
|
|
|
2508
3617
|
};
|
|
2509
3618
|
export const makeEffectQbSqliteOAuthAccountStore = (executor, options = {}) => {
|
|
2510
3619
|
const tables = options.tables ?? authSqliteTables;
|
|
2511
|
-
return OAuthAccountStore.
|
|
3620
|
+
return OAuthAccountStore.of({
|
|
2512
3621
|
insert: (row) => executePlan(executor, queryInsert(tables.oauthAccount.table, mutationValues(tables.oauthAccount.insert(row)))).pipe(Effect.mapError((cause) => oauthAccountStoreError("insert", cause)), Effect.asVoid),
|
|
2513
3622
|
findById: (id) => findOAuthAccountById(executor, tables, id),
|
|
2514
3623
|
findByProviderAccount: (input) => findOAuthAccountByProviderAccount(executor, tables, input),
|
|
@@ -2532,9 +3641,90 @@ export const makeEffectQbSqliteOAuthAccountStore = (executor, options = {}) => {
|
|
|
2532
3641
|
})),
|
|
2533
3642
|
});
|
|
2534
3643
|
};
|
|
3644
|
+
export const makeEffectQbSqliteOAuthIdentityBridgeStore = (executor, atomic, options = {}) => {
|
|
3645
|
+
const tables = options.tables ?? authSqliteTables;
|
|
3646
|
+
const identities = makeEffectQbSqliteIdentityStore(executor, atomic, options);
|
|
3647
|
+
const accounts = makeEffectQbSqliteOAuthAccountStore(executor, options);
|
|
3648
|
+
const classify = (input, cause) => Effect.gen(function* () {
|
|
3649
|
+
if (Option.isSome(yield* findUserById(executor, tables, input.user.id))) {
|
|
3650
|
+
return yield* new OAuthIdentityBridgeConflictError({
|
|
3651
|
+
reason: "user-id",
|
|
3652
|
+
});
|
|
3653
|
+
}
|
|
3654
|
+
if (input.identity !== undefined) {
|
|
3655
|
+
if (Option.isSome(yield* identities.findById(input.identity.id))) {
|
|
3656
|
+
return yield* new OAuthIdentityBridgeConflictError({
|
|
3657
|
+
reason: "identity-id",
|
|
3658
|
+
});
|
|
3659
|
+
}
|
|
3660
|
+
if (Option.isSome(yield* identities.findByKindAndNormalizedValue({
|
|
3661
|
+
scope: input.identity.scope,
|
|
3662
|
+
kind: input.identity.kind,
|
|
3663
|
+
normalizedValue: input.identity.normalizedValue,
|
|
3664
|
+
}))) {
|
|
3665
|
+
return yield* new OAuthIdentityBridgeConflictError({
|
|
3666
|
+
reason: "identity-value",
|
|
3667
|
+
});
|
|
3668
|
+
}
|
|
3669
|
+
}
|
|
3670
|
+
if (Option.isSome(yield* accounts.findById(input.account.id))) {
|
|
3671
|
+
return yield* new OAuthIdentityBridgeConflictError({
|
|
3672
|
+
reason: "oauth-account-id",
|
|
3673
|
+
});
|
|
3674
|
+
}
|
|
3675
|
+
if (Option.isSome(yield* accounts.findByProviderAccount({
|
|
3676
|
+
providerId: input.account.providerId,
|
|
3677
|
+
providerAccountId: input.account.providerAccountId,
|
|
3678
|
+
includeUnlinked: true,
|
|
3679
|
+
}))) {
|
|
3680
|
+
return yield* new OAuthIdentityBridgeConflictError({
|
|
3681
|
+
reason: "provider-account",
|
|
3682
|
+
});
|
|
3683
|
+
}
|
|
3684
|
+
return yield* new OAuthIdentityBridgeStoreError({
|
|
3685
|
+
message: "Atomic OAuth identity creation failed",
|
|
3686
|
+
cause,
|
|
3687
|
+
});
|
|
3688
|
+
}).pipe(Effect.catch((error) => error instanceof OAuthIdentityBridgeConflictError ||
|
|
3689
|
+
error instanceof OAuthIdentityBridgeStoreError
|
|
3690
|
+
? Effect.fail(error)
|
|
3691
|
+
: Effect.fail(new OAuthIdentityBridgeStoreError({
|
|
3692
|
+
message: "OAuth identity conflict classification failed",
|
|
3693
|
+
cause: error,
|
|
3694
|
+
}))));
|
|
3695
|
+
return OAuthIdentityBridgeStore.of({
|
|
3696
|
+
create: (input) => {
|
|
3697
|
+
if (input.account.userId !== input.user.id ||
|
|
3698
|
+
(input.identity !== undefined &&
|
|
3699
|
+
input.identity.userId !== input.user.id)) {
|
|
3700
|
+
return Effect.fail(new OAuthIdentityBridgeConflictError({ reason: "user-id" }));
|
|
3701
|
+
}
|
|
3702
|
+
const plans = [
|
|
3703
|
+
queryInsert(tables.user.table, mutationValues(tables.user.insert(input.user))),
|
|
3704
|
+
];
|
|
3705
|
+
if (input.identity !== undefined) {
|
|
3706
|
+
plans.push(queryInsert(tables.identity.table, mutationValues(tables.identity.insert(input.identity))));
|
|
3707
|
+
}
|
|
3708
|
+
plans.push(queryInsert(tables.oauthAccount.table, mutationValues(tables.oauthAccount.insert(input.account))));
|
|
3709
|
+
return atomic.execute(plans).pipe(Effect.map(() => ({
|
|
3710
|
+
user: input.user,
|
|
3711
|
+
...(input.identity === undefined
|
|
3712
|
+
? {}
|
|
3713
|
+
: {
|
|
3714
|
+
identity: {
|
|
3715
|
+
...input.identity,
|
|
3716
|
+
isPrimaryLogin: input.identity.isPrimaryLogin ?? false,
|
|
3717
|
+
updatedAt: input.identity.createdAt,
|
|
3718
|
+
},
|
|
3719
|
+
}),
|
|
3720
|
+
account: input.account,
|
|
3721
|
+
})), Effect.catch((error) => classify(input, error)));
|
|
3722
|
+
},
|
|
3723
|
+
});
|
|
3724
|
+
};
|
|
2535
3725
|
export const makeEffectQbSqliteOAuthClientStore = (executor, options = {}) => {
|
|
2536
3726
|
const tables = options.tables ?? authSqliteTables;
|
|
2537
|
-
return OAuthClientStore.
|
|
3727
|
+
return OAuthClientStore.of({
|
|
2538
3728
|
upsert: (record) => executePlan(executor, queryInsert(tables.oauthClient.table, mutationValues(tables.oauthClient.insert(record))).pipe(Query.onConflict("id", {
|
|
2539
3729
|
update: mutationValues(tables.oauthClient.update(record)),
|
|
2540
3730
|
}))).pipe(Effect.mapError((cause) => oauthClientStoreError("upsert", cause)), Effect.asVoid),
|
|
@@ -2551,7 +3741,7 @@ export const makeEffectQbSqliteOAuthClientStore = (executor, options = {}) => {
|
|
|
2551
3741
|
};
|
|
2552
3742
|
export const makeEffectQbSqliteOAuthConsentStore = (executor, options = {}) => {
|
|
2553
3743
|
const tables = options.tables ?? authSqliteTables;
|
|
2554
|
-
return OAuthConsentStore.
|
|
3744
|
+
return OAuthConsentStore.of({
|
|
2555
3745
|
upsert: (record) => executePlan(executor, queryInsert(tables.oauthConsent.table, mutationValues(tables.oauthConsent.insert(record))).pipe(Query.onConflict("id", {
|
|
2556
3746
|
update: mutationValues(tables.oauthConsent.update(record)),
|
|
2557
3747
|
}))).pipe(Effect.mapError((cause) => oauthConsentStoreError("upsert", cause)), Effect.asVoid),
|
|
@@ -2575,12 +3765,14 @@ export const makeEffectQbSqliteOAuthConsentStore = (executor, options = {}) => {
|
|
|
2575
3765
|
};
|
|
2576
3766
|
export const makeEffectQbSqliteOAuthAuthorizationCodeStore = (executor, options = {}) => {
|
|
2577
3767
|
const tables = options.tables ?? authSqliteTables;
|
|
2578
|
-
return OAuthAuthorizationCodeStore.
|
|
3768
|
+
return OAuthAuthorizationCodeStore.of({
|
|
2579
3769
|
upsert: (record) => executePlan(executor, queryInsert(tables.oauthAuthorizationCode.table, mutationValues(tables.oauthAuthorizationCode.insert(record))).pipe(Query.onConflict("code_hash", {
|
|
2580
3770
|
update: mutationValues(tables.oauthAuthorizationCode.update(record)),
|
|
2581
3771
|
}))).pipe(Effect.mapError((cause) => oauthAuthorizationCodeStoreError("upsert", cause)), Effect.asVoid),
|
|
2582
3772
|
findByHash: (input) => findOAuthAuthorizationCodeByHash(executor, tables, input),
|
|
2583
|
-
markConsumed: (input) => executePlan(executor, queryUpdate(tables.oauthAuthorizationCode.table, mutationValues(tables.oauthAuthorizationCode.markConsumed(input))).pipe(Query.where(queryEq(tables.oauthAuthorizationCode.select.codeHash, input.codeHash)), Query.where(queryIsNull(tables.oauthAuthorizationCode.select.consumedAt)), Query.returning({
|
|
3773
|
+
markConsumed: (input) => executePlan(executor, queryUpdate(tables.oauthAuthorizationCode.table, mutationValues(tables.oauthAuthorizationCode.markConsumed(input))).pipe(Query.where(queryEq(tables.oauthAuthorizationCode.select.codeHash, input.codeHash)), Query.where(queryIsNull(tables.oauthAuthorizationCode.select.consumedAt)), Query.returning({
|
|
3774
|
+
codeHash: tables.oauthAuthorizationCode.select.codeHash,
|
|
3775
|
+
}))).pipe(Effect.mapError((cause) => oauthAuthorizationCodeStoreError("mark-consumed", cause)), Effect.flatMap((rows) => {
|
|
2584
3776
|
const [consumed] = rows;
|
|
2585
3777
|
if (consumed !== undefined) {
|
|
2586
3778
|
return findOAuthAuthorizationCodeByHash(executor, tables, { codeHash: consumed.codeHash }, "mark-consumed").pipe(Effect.map((record) => Option.isNone(record)
|
|
@@ -2601,8 +3793,8 @@ export const makeEffectQbSqliteOAuthAuthorizationCodeStore = (executor, options
|
|
|
2601
3793
|
};
|
|
2602
3794
|
export const makeEffectQbSqliteOAuthClientSecretStore = (executor, options = {}) => {
|
|
2603
3795
|
const tables = options.tables ?? authSqliteTables;
|
|
2604
|
-
return OAuthClientSecretStore.
|
|
2605
|
-
upsert: (record) => executePlan(executor, queryInsert(tables.oauthClientSecret.table, mutationValues(tables.oauthClientSecret.insert(record))).pipe(Query.onConflict("prefix", {
|
|
3796
|
+
return OAuthClientSecretStore.of({
|
|
3797
|
+
upsert: (record) => executePlan(executor, queryInsert(tables.oauthClientSecret.table, mutationValues(tables.oauthClientSecret.insert(record))).pipe(Query.onConflict(["client_id", "prefix"], {
|
|
2606
3798
|
update: mutationValues(tables.oauthClientSecret.update(record)),
|
|
2607
3799
|
}))).pipe(Effect.mapError((cause) => oauthClientSecretStoreError("upsert", cause)), Effect.asVoid),
|
|
2608
3800
|
findByClientIdAndPrefix: (input) => findOAuthClientSecretByClientIdAndPrefix(executor, tables, input),
|
|
@@ -2621,7 +3813,7 @@ export const makeEffectQbSqliteOAuthClientSecretStore = (executor, options = {})
|
|
|
2621
3813
|
};
|
|
2622
3814
|
export const makeEffectQbSqliteOAuthProviderModeTokenStore = (executor, options = {}) => {
|
|
2623
3815
|
const tables = options.tables ?? authSqliteTables;
|
|
2624
|
-
return OAuthProviderModeTokenStore.
|
|
3816
|
+
return OAuthProviderModeTokenStore.of({
|
|
2625
3817
|
upsert: (record) => executePlan(executor, queryInsert(tables.oauthProviderModeToken.table, mutationValues(tables.oauthProviderModeToken.insert(record))).pipe(Query.onConflict("token_hash", {
|
|
2626
3818
|
update: mutationValues(tables.oauthProviderModeToken.update(record)),
|
|
2627
3819
|
}))).pipe(Effect.mapError((cause) => oauthProviderModeTokenStoreError("upsert", cause)), Effect.asVoid),
|
|
@@ -2646,7 +3838,7 @@ export const makeEffectQbSqliteOAuthProviderModeTokenStore = (executor, options
|
|
|
2646
3838
|
};
|
|
2647
3839
|
export const makeEffectQbSqliteOAuthProviderTokenVault = (executor, options = {}) => {
|
|
2648
3840
|
const tables = options.tables ?? authSqliteTables;
|
|
2649
|
-
return OAuthProviderTokenVault.
|
|
3841
|
+
return OAuthProviderTokenVault.of({
|
|
2650
3842
|
upsert: (record) => findOAuthProviderTokenVaultRecordByAccountId(executor, tables, { accountId: record.accountId }, "upsert", true).pipe(Effect.flatMap((existing) => Option.isNone(existing)
|
|
2651
3843
|
? executePlan(executor, queryInsert(tables.oauthProviderTokenVault.table, mutationValues(tables.oauthProviderTokenVault.insert(record))))
|
|
2652
3844
|
: executePlan(executor, queryUpdate(tables.oauthProviderTokenVault.table, mutationValues(tables.oauthProviderTokenVault.update(record))).pipe(Query.where(queryEq(tables.oauthProviderTokenVault.select.accountId, record.accountId))))), Effect.mapError((cause) => cause instanceof OAuthProviderTokenVaultError
|
|
@@ -2671,7 +3863,7 @@ export const makeEffectQbSqliteOAuthProviderTokenVault = (executor, options = {}
|
|
|
2671
3863
|
};
|
|
2672
3864
|
export const makeEffectQbSqliteDomainVerificationStore = (executor, options = {}) => {
|
|
2673
3865
|
const tables = options.tables ?? authSqliteTables;
|
|
2674
|
-
return DomainVerificationStore.
|
|
3866
|
+
return DomainVerificationStore.of({
|
|
2675
3867
|
upsert: (record) => findDomainVerificationByOwnerAndDomain(executor, tables, { ownerId: record.ownerId, domain: record.domain }, "upsert").pipe(Effect.flatMap((existing) => Option.isNone(existing)
|
|
2676
3868
|
? executePlan(executor, queryInsert(tables.domainVerification.table, mutationValues(tables.domainVerification.insert(record))))
|
|
2677
3869
|
: executePlan(executor, queryUpdate(tables.domainVerification.table, mutationValues(tables.domainVerification.update(record))).pipe(Query.where(queryEq(tables.domainVerification.select.ownerId, record.ownerId)), Query.where(queryEq(tables.domainVerification.select.domain, record.domain))))), Effect.mapError((cause) => cause instanceof DomainVerificationStoreError
|
|
@@ -2683,8 +3875,8 @@ export const makeEffectQbSqliteDomainVerificationStore = (executor, options = {}
|
|
|
2683
3875
|
const base = Query.select(selectDomainVerificationColumns(tables)).pipe(Query.from(tables.domainVerification.table), Query.where(queryEq(tables.domainVerification.select.ownerId, input.ownerId)));
|
|
2684
3876
|
const query = input.includeRevoked === true
|
|
2685
3877
|
? base
|
|
2686
|
-
: base.pipe(Query.where(
|
|
2687
|
-
return executePlan(executor, query).pipe(Effect.mapError((cause) => domainVerificationStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodeDomainVerificationRecord(tables, row))));
|
|
3878
|
+
: base.pipe(Query.where(queryNeq(tables.domainVerification.select.status, "revoked")));
|
|
3879
|
+
return executePlan(executor, query.pipe(Query.orderBy(tables.domainVerification.select.createdAt, "asc"), Query.orderBy(tables.domainVerification.select.id, "asc"))).pipe(Effect.mapError((cause) => domainVerificationStoreError("list", cause)), Effect.map((rows) => rows.map((row) => decodeDomainVerificationRecord(tables, row))));
|
|
2688
3880
|
},
|
|
2689
3881
|
markVerified: (input) => executePlan(executor, queryUpdate(tables.domainVerification.table, mutationValues(tables.domainVerification.markVerified(input))).pipe(Query.where(queryEq(tables.domainVerification.select.id, input.id)), Query.where(queryEq(tables.domainVerification.select.status, "pending")), Query.returning(selectDomainVerificationColumns(tables)))).pipe(Effect.mapError((cause) => domainVerificationStoreError("mark-verified", cause)), Effect.map((rows) => rows[0] === undefined
|
|
2690
3882
|
? Option.none()
|
|
@@ -2699,7 +3891,7 @@ export const makeEffectQbSqliteDomainVerificationStore = (executor, options = {}
|
|
|
2699
3891
|
};
|
|
2700
3892
|
export const makeEffectQbSqliteSecurityTimelineStore = (executor, options = {}) => {
|
|
2701
3893
|
const tables = options.tables ?? authSqliteTables;
|
|
2702
|
-
return SecurityTimelineStore.
|
|
3894
|
+
return SecurityTimelineStore.of({
|
|
2703
3895
|
insert: (event) => executePlan(executor, queryInsert(tables.securityTimeline.table, mutationValues(tables.securityTimeline.insert(event)))).pipe(Effect.mapError((cause) => securityTimelineStoreError("insert", cause)), Effect.asVoid),
|
|
2704
3896
|
listByUser: (input) => listSecurityTimelineByUser(executor, tables, input),
|
|
2705
3897
|
deleteBefore: (input) => deleteSecurityTimelineBefore(executor, tables, input),
|
|
@@ -2707,7 +3899,7 @@ export const makeEffectQbSqliteSecurityTimelineStore = (executor, options = {})
|
|
|
2707
3899
|
};
|
|
2708
3900
|
export const makeEffectQbSqliteWebhookOutboxStore = (executor, options = {}) => {
|
|
2709
3901
|
const tables = options.tables ?? authSqliteTables;
|
|
2710
|
-
return WebhookOutboxStore.
|
|
3902
|
+
return WebhookOutboxStore.of({
|
|
2711
3903
|
enqueue: (input) => Effect.gen(function* () {
|
|
2712
3904
|
const now = input.now ?? (yield* currentUnixMillis);
|
|
2713
3905
|
const record = {
|
|
@@ -2726,7 +3918,8 @@ export const makeEffectQbSqliteWebhookOutboxStore = (executor, options = {}) =>
|
|
|
2726
3918
|
claimDue: (input) => claimDueWebhookOutboxRecords(executor, tables, input),
|
|
2727
3919
|
markDelivered: (input) => Effect.gen(function* () {
|
|
2728
3920
|
const existing = yield* findWebhookOutboxRecordById(executor, tables, input.id, "markDelivered");
|
|
2729
|
-
if (Option.isNone(existing) ||
|
|
3921
|
+
if (Option.isNone(existing) ||
|
|
3922
|
+
existing.value.status === "dead_lettered") {
|
|
2730
3923
|
return;
|
|
2731
3924
|
}
|
|
2732
3925
|
const deliveredAt = input.deliveredAt ?? (yield* currentUnixMillis);
|
|
@@ -2764,7 +3957,7 @@ export const makeEffectQbSqliteWebhookOutboxStore = (executor, options = {}) =>
|
|
|
2764
3957
|
};
|
|
2765
3958
|
export const makeEffectQbSqliteWebhookReplayStore = (executor, options = {}) => {
|
|
2766
3959
|
const tables = options.tables ?? authSqliteTables;
|
|
2767
|
-
return WebhookReplayStore.
|
|
3960
|
+
return WebhookReplayStore.of({
|
|
2768
3961
|
claim: (input) => Effect.gen(function* () {
|
|
2769
3962
|
const now = input.now ?? (yield* currentUnixMillis);
|
|
2770
3963
|
const claim = { ...input, now };
|
|
@@ -2777,8 +3970,297 @@ export const makeEffectQbSqliteWebhookReplayStore = (executor, options = {}) =>
|
|
|
2777
3970
|
deleteExpired: (input = {}) => deleteExpiredWebhookReplayRecords(executor, tables, input),
|
|
2778
3971
|
});
|
|
2779
3972
|
};
|
|
2780
|
-
|
|
3973
|
+
const permissionScopeKey = (scope) => [
|
|
3974
|
+
scope?.type ?? "global",
|
|
3975
|
+
scope?.id === undefined ? 0 : 1,
|
|
3976
|
+
scope?.id ?? "",
|
|
3977
|
+
];
|
|
3978
|
+
const comparePermissionText = (left, right) => left === right
|
|
3979
|
+
? 0
|
|
3980
|
+
: left === undefined
|
|
3981
|
+
? -1
|
|
3982
|
+
: right === undefined
|
|
3983
|
+
? 1
|
|
3984
|
+
: left < right
|
|
3985
|
+
? -1
|
|
3986
|
+
: 1;
|
|
3987
|
+
const comparePermissionScopes = (left, right) => {
|
|
3988
|
+
if (left === undefined || right === undefined) {
|
|
3989
|
+
return left === right ? 0 : left === undefined ? -1 : 1;
|
|
3990
|
+
}
|
|
3991
|
+
return (comparePermissionText(left.type, right.type) ||
|
|
3992
|
+
comparePermissionText(left.id, right.id));
|
|
3993
|
+
};
|
|
3994
|
+
const permissionScopesMatchExactly = (left, right) => {
|
|
3995
|
+
const [leftType, leftIdPresent, leftId] = permissionScopeKey(left);
|
|
3996
|
+
const [rightType, rightIdPresent, rightId] = permissionScopeKey(right);
|
|
3997
|
+
return (leftType === rightType &&
|
|
3998
|
+
leftIdPresent === rightIdPresent &&
|
|
3999
|
+
leftId === rightId);
|
|
4000
|
+
};
|
|
4001
|
+
const permissionGrantIsActive = (grant, at) => grant.revokedAt === undefined &&
|
|
4002
|
+
(grant.expiresAt === undefined || Number(grant.expiresAt) > Number(at));
|
|
4003
|
+
const comparePermissionGrantRecords = (left, right) => comparePermissionText(left.subject.type, right.subject.type) ||
|
|
4004
|
+
comparePermissionText(left.subject.id, right.subject.id) ||
|
|
4005
|
+
comparePermissionText(left.permission, right.permission) ||
|
|
4006
|
+
comparePermissionScopes(left.scope, right.scope);
|
|
4007
|
+
const compareRoleGrantRecords = (left, right) => comparePermissionText(left.subject.type, right.subject.type) ||
|
|
4008
|
+
comparePermissionText(left.subject.id, right.subject.id) ||
|
|
4009
|
+
comparePermissionText(left.role, right.role) ||
|
|
4010
|
+
comparePermissionScopes(left.scope, right.scope);
|
|
4011
|
+
const compareRolePermissionRecords = (left, right) => comparePermissionText(left.role, right.role) ||
|
|
4012
|
+
comparePermissionText(left.permission, right.permission) ||
|
|
4013
|
+
comparePermissionText(left.scopeType, right.scopeType);
|
|
4014
|
+
export const makeEffectQbSqlitePermissionStore = (executor, options = {}) => {
|
|
4015
|
+
const tables = options.tables ?? authSqliteTables;
|
|
4016
|
+
const permissionGrantWhere = (input) => {
|
|
4017
|
+
const { scope: _scope, ...withoutScope } = input;
|
|
4018
|
+
const scope = canonicalPermissionScope(input.scope);
|
|
4019
|
+
const canonicalInput = {
|
|
4020
|
+
...withoutScope,
|
|
4021
|
+
...(scope === undefined ? {} : { scope }),
|
|
4022
|
+
};
|
|
4023
|
+
const [scopeType, scopeIdPresent, scopeId] = permissionScopeKey(scope);
|
|
4024
|
+
return queryUpdate(tables.permissionGrant.table, mutationValues(tables.permissionGrant.revoke(canonicalInput, input.revokedAt ?? UnixMillis(Date.now())))).pipe(Query.where(queryEq(tables.permissionGrant.select.subjectType, input.subject.type)), Query.where(queryEq(tables.permissionGrant.select.subjectId, input.subject.id)), Query.where(queryEq(tables.permissionGrant.select.permissionId, input.permission)), Query.where(queryEq(tables.permissionGrant.select.scopeType, scopeType)), Query.where(queryEq(tables.permissionGrant.select.scopeIdPresent, scopeIdPresent)), Query.where(queryEq(tables.permissionGrant.select.scopeId, scopeId)));
|
|
4025
|
+
};
|
|
4026
|
+
const roleGrantWhere = (input) => {
|
|
4027
|
+
const { scope: _scope, ...withoutScope } = input;
|
|
4028
|
+
const scope = canonicalPermissionScope(input.scope);
|
|
4029
|
+
const canonicalInput = {
|
|
4030
|
+
...withoutScope,
|
|
4031
|
+
...(scope === undefined ? {} : { scope }),
|
|
4032
|
+
};
|
|
4033
|
+
const [scopeType, scopeIdPresent, scopeId] = permissionScopeKey(scope);
|
|
4034
|
+
return queryUpdate(tables.roleGrant.table, mutationValues(tables.roleGrant.revoke(canonicalInput, input.revokedAt ?? UnixMillis(Date.now())))).pipe(Query.where(queryEq(tables.roleGrant.select.subjectType, input.subject.type)), Query.where(queryEq(tables.roleGrant.select.subjectId, input.subject.id)), Query.where(queryEq(tables.roleGrant.select.roleId, input.role)), Query.where(queryEq(tables.roleGrant.select.scopeType, scopeType)), Query.where(queryEq(tables.roleGrant.select.scopeIdPresent, scopeIdPresent)), Query.where(queryEq(tables.roleGrant.select.scopeId, scopeId)));
|
|
4035
|
+
};
|
|
4036
|
+
const checkResult = (operation, allowed) => executePlan(executor, Query.select({ allowed: allowed })).pipe(Effect.mapError((cause) => permissionStoreError(operation, cause)), Effect.map((rows) => decodeBoolean(rows[0]?.allowed)));
|
|
4037
|
+
const findPermissionDefinition = (id, operation = "find_permission_definition") => executePlan(executor, Query.select(selectPermissionDefinitionColumns(tables)).pipe(Query.from(tables.permissionDefinition.table), Query.where(queryEq(tables.permissionDefinition.select.id, id)))).pipe(Effect.mapError((cause) => permissionStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
|
|
4038
|
+
? Option.none()
|
|
4039
|
+
: Option.some(decodePermissionDefinition(tables, rows[0]))));
|
|
4040
|
+
const findRoleDefinition = (id, operation = "find_role_definition") => executePlan(executor, Query.select(selectRoleDefinitionColumns(tables)).pipe(Query.from(tables.roleDefinition.table), Query.where(queryEq(tables.roleDefinition.select.id, id)))).pipe(Effect.mapError((cause) => permissionStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
|
|
4041
|
+
? Option.none()
|
|
4042
|
+
: Option.some(decodeRoleDefinition(tables, rows[0]))));
|
|
4043
|
+
const mutatePermissionDefinition = (operation, input, values) => {
|
|
4044
|
+
const returning = operation === "delete_permission_definition"
|
|
4045
|
+
? {
|
|
4046
|
+
...selectPermissionDefinitionColumns(tables),
|
|
4047
|
+
deletedAt: queryWithoutNullRefinement(tables.permissionDefinition.select.deletedAt),
|
|
4048
|
+
}
|
|
4049
|
+
: selectPermissionDefinitionColumns(tables);
|
|
4050
|
+
return executePlan(executor, queryUpdate(tables.permissionDefinition.table, mutationValues(values)).pipe(Query.where(queryEq(tables.permissionDefinition.select.id, input.id)), Query.where(queryEq(tables.permissionDefinition.select.updatedAt, Number(input.expectedUpdatedAt))), Query.where(queryIsNull(tables.permissionDefinition.select.deletedAt)), Query.returning(returning))).pipe(Effect.mapError((cause) => permissionStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
|
|
4051
|
+
? Option.none()
|
|
4052
|
+
: Option.some(decodePermissionDefinition(tables, rows[0]))));
|
|
4053
|
+
};
|
|
4054
|
+
const mutateRoleDefinition = (operation, input, values) => {
|
|
4055
|
+
const returning = operation === "delete_role_definition"
|
|
4056
|
+
? {
|
|
4057
|
+
...selectRoleDefinitionColumns(tables),
|
|
4058
|
+
deletedAt: queryWithoutNullRefinement(tables.roleDefinition.select.deletedAt),
|
|
4059
|
+
}
|
|
4060
|
+
: selectRoleDefinitionColumns(tables);
|
|
4061
|
+
return executePlan(executor, queryUpdate(tables.roleDefinition.table, mutationValues(values)).pipe(Query.where(queryEq(tables.roleDefinition.select.id, input.id)), Query.where(queryEq(tables.roleDefinition.select.updatedAt, Number(input.expectedUpdatedAt))), Query.where(queryIsNull(tables.roleDefinition.select.deletedAt)), Query.returning(returning))).pipe(Effect.mapError((cause) => permissionStoreError(operation, cause)), Effect.map((rows) => rows[0] === undefined
|
|
4062
|
+
? Option.none()
|
|
4063
|
+
: Option.some(decodeRoleDefinition(tables, rows[0]))));
|
|
4064
|
+
};
|
|
4065
|
+
return PermissionStore.of({
|
|
4066
|
+
createPermissionDefinition: (definition) => executePlan(executor, queryInsert(tables.permissionDefinition.table, mutationValues(tables.permissionDefinition.insert(definition))).pipe(SqliteQuery.onConflict(tables.permissionDefinition.conflictTarget), Query.returning(selectPermissionDefinitionColumns(tables)))).pipe(Effect.mapError((cause) => permissionStoreError("create_permission_definition", cause)), Effect.map((rows) => rows.length > 0)),
|
|
4067
|
+
findPermissionDefinition: (id) => findPermissionDefinition(id),
|
|
4068
|
+
listPermissionDefinitions: (input = {}) => {
|
|
4069
|
+
const base = Query.select(selectPermissionDefinitionColumns(tables)).pipe(Query.from(tables.permissionDefinition.table));
|
|
4070
|
+
const withoutDisabled = input.includeDisabled === true
|
|
4071
|
+
? base
|
|
4072
|
+
: base.pipe(Query.where(queryIsNull(tables.permissionDefinition.select.disabledAt)));
|
|
4073
|
+
const withoutDeleted = input.includeDeleted === true
|
|
4074
|
+
? withoutDisabled
|
|
4075
|
+
: withoutDisabled.pipe(Query.where(queryIsNull(tables.permissionDefinition.select.deletedAt)));
|
|
4076
|
+
const after = input.after === undefined
|
|
4077
|
+
? withoutDeleted
|
|
4078
|
+
: withoutDeleted.pipe(Query.where(queryGt(tables.permissionDefinition.select.id, input.after)));
|
|
4079
|
+
const query = after.pipe(Query.orderBy(tables.permissionDefinition.select.id, "asc"), Query.limit(normalizePermissionDefinitionListLimit(input.limit)));
|
|
4080
|
+
return executePlan(executor, query).pipe(Effect.mapError((cause) => permissionStoreError("list_permission_definitions", cause)), Effect.map((rows) => rows.map((row) => decodePermissionDefinition(tables, row))));
|
|
4081
|
+
},
|
|
4082
|
+
updatePermissionDefinition: (input) => mutatePermissionDefinition("update_permission_definition", input, tables.permissionDefinition.update(input)),
|
|
4083
|
+
setPermissionDefinitionDisabled: (input) => mutatePermissionDefinition("set_permission_definition_disabled", input, tables.permissionDefinition.setDisabled(input)),
|
|
4084
|
+
deletePermissionDefinition: (input) => mutatePermissionDefinition("delete_permission_definition", input, tables.permissionDefinition.delete(input)),
|
|
4085
|
+
createRoleDefinition: (definition) => executePlan(executor, queryInsert(tables.roleDefinition.table, mutationValues(tables.roleDefinition.insert(definition))).pipe(SqliteQuery.onConflict(tables.roleDefinition.conflictTarget), Query.returning(selectRoleDefinitionColumns(tables)))).pipe(Effect.mapError((cause) => permissionStoreError("create_role_definition", cause)), Effect.map((rows) => rows.length > 0)),
|
|
4086
|
+
findRoleDefinition: (id) => findRoleDefinition(id),
|
|
4087
|
+
listRoleDefinitions: (input = {}) => {
|
|
4088
|
+
const base = Query.select(selectRoleDefinitionColumns(tables)).pipe(Query.from(tables.roleDefinition.table));
|
|
4089
|
+
const withoutDisabled = input.includeDisabled === true
|
|
4090
|
+
? base
|
|
4091
|
+
: base.pipe(Query.where(queryIsNull(tables.roleDefinition.select.disabledAt)));
|
|
4092
|
+
const query = input.includeDeleted === true
|
|
4093
|
+
? withoutDisabled
|
|
4094
|
+
: withoutDisabled.pipe(Query.where(queryIsNull(tables.roleDefinition.select.deletedAt)));
|
|
4095
|
+
return executePlan(executor, query).pipe(Effect.mapError((cause) => permissionStoreError("list_role_definitions", cause)), Effect.map((rows) => rows
|
|
4096
|
+
.map((row) => decodeRoleDefinition(tables, row))
|
|
4097
|
+
.sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0)));
|
|
4098
|
+
},
|
|
4099
|
+
updateRoleDefinition: (input) => mutateRoleDefinition("update_role_definition", input, tables.roleDefinition.update(input)),
|
|
4100
|
+
setRoleDefinitionDisabled: (input) => mutateRoleDefinition("set_role_definition_disabled", input, tables.roleDefinition.setDisabled(input)),
|
|
4101
|
+
deleteRoleDefinition: (input) => mutateRoleDefinition("delete_role_definition", input, tables.roleDefinition.delete(input)),
|
|
4102
|
+
grantPermission: (input) => Effect.suspend(() => {
|
|
4103
|
+
const { scope: _scope, ...withoutScope } = input;
|
|
4104
|
+
const scope = canonicalPermissionScope(input.scope);
|
|
4105
|
+
const grant = {
|
|
4106
|
+
...withoutScope,
|
|
4107
|
+
...(scope === undefined ? {} : { scope }),
|
|
4108
|
+
};
|
|
4109
|
+
return executePlan(executor, queryInsert(tables.permissionGrant.table, mutationValues(tables.permissionGrant.insert(grant))).pipe(SqliteQuery.onConflict([
|
|
4110
|
+
"subject_type",
|
|
4111
|
+
"subject_id",
|
|
4112
|
+
"permission_id",
|
|
4113
|
+
"scope_type",
|
|
4114
|
+
"scope_id_present",
|
|
4115
|
+
"scope_id",
|
|
4116
|
+
], { update: mutationValues(tables.permissionGrant.update(grant)) }))).pipe(Effect.mapError((cause) => permissionStoreError("grant_permission", cause)), Effect.asVoid);
|
|
4117
|
+
}),
|
|
4118
|
+
revokePermission: (input) => Effect.suspend(() => executePlan(executor, permissionGrantWhere(input)).pipe(Effect.mapError((cause) => permissionStoreError("revoke_permission", cause)), Effect.asVoid)),
|
|
4119
|
+
listPermissionGrants: (input = {}) => Effect.suspend(() => {
|
|
4120
|
+
const at = input.at ?? UnixMillis(Date.now());
|
|
4121
|
+
const activity = input.activity ?? "active";
|
|
4122
|
+
const base = Query.select(selectPermissionGrantColumns(tables)).pipe(Query.from(tables.permissionGrant.table));
|
|
4123
|
+
const bySubjectType = input.subject === undefined
|
|
4124
|
+
? base
|
|
4125
|
+
: base.pipe(Query.where(queryEq(tables.permissionGrant.select.subjectType, input.subject.type)));
|
|
4126
|
+
const bySubject = input.subject === undefined
|
|
4127
|
+
? bySubjectType
|
|
4128
|
+
: bySubjectType.pipe(Query.where(queryEq(tables.permissionGrant.select.subjectId, input.subject.id)));
|
|
4129
|
+
const byPermission = input.permission === undefined
|
|
4130
|
+
? bySubject
|
|
4131
|
+
: bySubject.pipe(Query.where(queryEq(tables.permissionGrant.select.permissionId, input.permission)));
|
|
4132
|
+
const [scopeType, scopeIdPresent, scopeId] = permissionScopeKey(input.scope);
|
|
4133
|
+
const query = input.scope === undefined
|
|
4134
|
+
? byPermission
|
|
4135
|
+
: byPermission.pipe(Query.where(queryEq(tables.permissionGrant.select.scopeType, scopeType)), Query.where(queryEq(tables.permissionGrant.select.scopeIdPresent, scopeIdPresent)), Query.where(queryEq(tables.permissionGrant.select.scopeId, scopeId)));
|
|
4136
|
+
return executePlan(executor, query).pipe(Effect.mapError((cause) => permissionStoreError("list_permission_grants", cause)), Effect.flatMap((rows) => Effect.try({
|
|
4137
|
+
try: () => rows
|
|
4138
|
+
.map((row) => decodePermissionGrant(tables, row))
|
|
4139
|
+
.filter((grant) => (input.subject === undefined ||
|
|
4140
|
+
(grant.subject.type === input.subject.type &&
|
|
4141
|
+
grant.subject.id === input.subject.id)) &&
|
|
4142
|
+
(input.permission === undefined ||
|
|
4143
|
+
grant.permission === input.permission) &&
|
|
4144
|
+
(input.scope === undefined ||
|
|
4145
|
+
permissionScopesMatchExactly(grant.scope, input.scope)) &&
|
|
4146
|
+
(activity === "all" ||
|
|
4147
|
+
permissionGrantIsActive(grant, at) ===
|
|
4148
|
+
(activity === "active")))
|
|
4149
|
+
.sort(comparePermissionGrantRecords),
|
|
4150
|
+
catch: (cause) => permissionStoreError("list_permission_grants", cause),
|
|
4151
|
+
})));
|
|
4152
|
+
}),
|
|
4153
|
+
grantRole: (input) => Effect.suspend(() => {
|
|
4154
|
+
const { scope: _scope, ...withoutScope } = input;
|
|
4155
|
+
const scope = canonicalPermissionScope(input.scope);
|
|
4156
|
+
const grant = {
|
|
4157
|
+
...withoutScope,
|
|
4158
|
+
...(scope === undefined ? {} : { scope }),
|
|
4159
|
+
};
|
|
4160
|
+
return executePlan(executor, queryInsert(tables.roleGrant.table, mutationValues(tables.roleGrant.insert(grant))).pipe(SqliteQuery.onConflict([
|
|
4161
|
+
"subject_type",
|
|
4162
|
+
"subject_id",
|
|
4163
|
+
"role_id",
|
|
4164
|
+
"scope_type",
|
|
4165
|
+
"scope_id_present",
|
|
4166
|
+
"scope_id",
|
|
4167
|
+
], { update: mutationValues(tables.roleGrant.update(grant)) }))).pipe(Effect.mapError((cause) => permissionStoreError("grant_role", cause)), Effect.asVoid);
|
|
4168
|
+
}),
|
|
4169
|
+
revokeRole: (input) => Effect.suspend(() => executePlan(executor, roleGrantWhere(input)).pipe(Effect.mapError((cause) => permissionStoreError("revoke_role", cause)), Effect.asVoid)),
|
|
4170
|
+
listRoleGrants: (input = {}) => Effect.suspend(() => {
|
|
4171
|
+
const at = input.at ?? UnixMillis(Date.now());
|
|
4172
|
+
const activity = input.activity ?? "active";
|
|
4173
|
+
const base = Query.select(selectRoleGrantColumns(tables)).pipe(Query.from(tables.roleGrant.table));
|
|
4174
|
+
const bySubjectType = input.subject === undefined
|
|
4175
|
+
? base
|
|
4176
|
+
: base.pipe(Query.where(queryEq(tables.roleGrant.select.subjectType, input.subject.type)));
|
|
4177
|
+
const bySubject = input.subject === undefined
|
|
4178
|
+
? bySubjectType
|
|
4179
|
+
: bySubjectType.pipe(Query.where(queryEq(tables.roleGrant.select.subjectId, input.subject.id)));
|
|
4180
|
+
const byRole = input.role === undefined
|
|
4181
|
+
? bySubject
|
|
4182
|
+
: bySubject.pipe(Query.where(queryEq(tables.roleGrant.select.roleId, input.role)));
|
|
4183
|
+
const [scopeType, scopeIdPresent, scopeId] = permissionScopeKey(input.scope);
|
|
4184
|
+
const query = input.scope === undefined
|
|
4185
|
+
? byRole
|
|
4186
|
+
: byRole.pipe(Query.where(queryEq(tables.roleGrant.select.scopeType, scopeType)), Query.where(queryEq(tables.roleGrant.select.scopeIdPresent, scopeIdPresent)), Query.where(queryEq(tables.roleGrant.select.scopeId, scopeId)));
|
|
4187
|
+
return executePlan(executor, query).pipe(Effect.mapError((cause) => permissionStoreError("list_role_grants", cause)), Effect.flatMap((rows) => Effect.try({
|
|
4188
|
+
try: () => rows
|
|
4189
|
+
.map((row) => decodeRoleGrant(tables, row))
|
|
4190
|
+
.filter((grant) => (input.subject === undefined ||
|
|
4191
|
+
(grant.subject.type === input.subject.type &&
|
|
4192
|
+
grant.subject.id === input.subject.id)) &&
|
|
4193
|
+
(input.role === undefined || grant.role === input.role) &&
|
|
4194
|
+
(input.scope === undefined ||
|
|
4195
|
+
permissionScopesMatchExactly(grant.scope, input.scope)) &&
|
|
4196
|
+
(activity === "all" ||
|
|
4197
|
+
permissionGrantIsActive(grant, at) ===
|
|
4198
|
+
(activity === "active")))
|
|
4199
|
+
.sort(compareRoleGrantRecords),
|
|
4200
|
+
catch: (cause) => permissionStoreError("list_role_grants", cause),
|
|
4201
|
+
})));
|
|
4202
|
+
}),
|
|
4203
|
+
assignRolePermission: (input) => executePlan(executor, queryInsert(tables.rolePermission.table, mutationValues(tables.rolePermission.insert(input))).pipe(SqliteQuery.onConflict([
|
|
4204
|
+
"role_id",
|
|
4205
|
+
"permission_id",
|
|
4206
|
+
"scope_type_present",
|
|
4207
|
+
"scope_type",
|
|
4208
|
+
]))).pipe(Effect.mapError((cause) => permissionStoreError("assign_role_permission", cause)), Effect.asVoid),
|
|
4209
|
+
removeRolePermission: (input) => executePlan(executor, Query.delete(tables.rolePermission.table).pipe(Query.where(queryEq(tables.rolePermission.select.roleId, input.role)), Query.where(queryEq(tables.rolePermission.select.permissionId, input.permission)), Query.where(queryEq(tables.rolePermission.select.scopeTypePresent, input.scopeType === undefined ? 0 : 1)), Query.where(queryEq(tables.rolePermission.select.scopeType, input.scopeType ?? "")))).pipe(Effect.mapError((cause) => permissionStoreError("remove_role_permission", cause)), Effect.asVoid),
|
|
4210
|
+
listRolePermissions: (input = {}) => {
|
|
4211
|
+
const base = Query.select(selectRolePermissionColumns(tables)).pipe(Query.from(tables.rolePermission.table));
|
|
4212
|
+
const byRole = input.role === undefined
|
|
4213
|
+
? base
|
|
4214
|
+
: base.pipe(Query.where(queryEq(tables.rolePermission.select.roleId, input.role)));
|
|
4215
|
+
const byPermission = input.permission === undefined
|
|
4216
|
+
? byRole
|
|
4217
|
+
: byRole.pipe(Query.where(queryEq(tables.rolePermission.select.permissionId, input.permission)));
|
|
4218
|
+
const byScopePresence = input.scopeType === undefined
|
|
4219
|
+
? byPermission
|
|
4220
|
+
: byPermission.pipe(Query.where(queryEq(tables.rolePermission.select.scopeTypePresent, input.scopeType === null ? 0 : 1)));
|
|
4221
|
+
const query = input.scopeType === undefined || input.scopeType === null
|
|
4222
|
+
? byScopePresence
|
|
4223
|
+
: byScopePresence.pipe(Query.where(queryEq(tables.rolePermission.select.scopeType, input.scopeType)));
|
|
4224
|
+
return executePlan(executor, query).pipe(Effect.mapError((cause) => permissionStoreError("list_role_permissions", cause)), Effect.flatMap((rows) => Effect.try({
|
|
4225
|
+
try: () => rows
|
|
4226
|
+
.map((row) => decodeRolePermission(tables, row))
|
|
4227
|
+
.filter((rolePermission) => (input.role === undefined ||
|
|
4228
|
+
rolePermission.role === input.role) &&
|
|
4229
|
+
(input.permission === undefined ||
|
|
4230
|
+
rolePermission.permission === input.permission) &&
|
|
4231
|
+
(input.scopeType === undefined ||
|
|
4232
|
+
(input.scopeType === null
|
|
4233
|
+
? rolePermission.scopeType === undefined
|
|
4234
|
+
: rolePermission.scopeType === input.scopeType)))
|
|
4235
|
+
.sort(compareRolePermissionRecords),
|
|
4236
|
+
catch: (cause) => permissionStoreError("list_role_permissions", cause),
|
|
4237
|
+
})));
|
|
4238
|
+
},
|
|
4239
|
+
hasPermission: (input) => Effect.suspend(() => {
|
|
4240
|
+
const now = Date.now();
|
|
4241
|
+
const [scopeType, scopeIdPresent, scopeId] = permissionScopeKey(input.scope);
|
|
4242
|
+
const permissionScope = queryOr(queryAnd(queryEq(tables.permissionGrant.select.scopeType, "global"), queryEq(tables.permissionGrant.select.scopeIdPresent, 0)), queryAnd(queryEq(tables.permissionGrant.select.scopeType, scopeType), queryEq(tables.permissionGrant.select.scopeIdPresent, scopeIdPresent), queryEq(tables.permissionGrant.select.scopeId, scopeId)));
|
|
4243
|
+
const direct = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.permissionGrant.table), Query.where(queryEq(tables.permissionGrant.select.subjectType, input.subject.type)), Query.where(queryEq(tables.permissionGrant.select.subjectId, input.subject.id)), Query.where(queryEq(tables.permissionGrant.select.permissionId, input.permission)), Query.where(queryIsNull(tables.permissionGrant.select.revokedAt)), Query.where(queryOr(queryIsNull(tables.permissionGrant.select.expiresAt), queryGt(tables.permissionGrant.select.expiresAt, now))), Query.where(permissionScope)));
|
|
4244
|
+
const roleGrant = aliasTable(tables.roleGrant.table, "role_grant");
|
|
4245
|
+
const roleGrantColumn = (column) => aliasColumn(roleGrant, column);
|
|
4246
|
+
const rolePermission = aliasTable(tables.rolePermission.table, "role_permission");
|
|
4247
|
+
const rolePermissionColumn = (column) => aliasColumn(rolePermission, column);
|
|
4248
|
+
const rolePermissionExists = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(rolePermission), Query.where(queryEq(rolePermissionColumn(tables.rolePermission.select.roleId), roleGrantColumn(tables.roleGrant.select.roleId))), Query.where(queryEq(rolePermissionColumn(tables.rolePermission.select.permissionId), input.permission)), Query.where(queryOr(queryEq(rolePermissionColumn(tables.rolePermission.select.scopeTypePresent), 0), queryAnd(queryEq(rolePermissionColumn(tables.rolePermission.select.scopeTypePresent), 1), queryEq(rolePermissionColumn(tables.rolePermission.select.scopeType), scopeType))))));
|
|
4249
|
+
const role = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(roleGrant), Query.where(queryEq(roleGrantColumn(tables.roleGrant.select.subjectType), input.subject.type)), Query.where(queryEq(roleGrantColumn(tables.roleGrant.select.subjectId), input.subject.id)), Query.where(queryIsNull(roleGrantColumn(tables.roleGrant.select.revokedAt))), Query.where(queryOr(queryIsNull(roleGrantColumn(tables.roleGrant.select.expiresAt)), queryGt(roleGrantColumn(tables.roleGrant.select.expiresAt), now))), Query.where(queryOr(queryAnd(queryEq(roleGrantColumn(tables.roleGrant.select.scopeType), "global"), queryEq(roleGrantColumn(tables.roleGrant.select.scopeIdPresent), 0)), queryAnd(queryEq(roleGrantColumn(tables.roleGrant.select.scopeType), scopeType), queryEq(roleGrantColumn(tables.roleGrant.select.scopeIdPresent), scopeIdPresent), queryEq(roleGrantColumn(tables.roleGrant.select.scopeId), scopeId)))), Query.where(rolePermissionExists)));
|
|
4250
|
+
return checkResult("has_permission", queryOr(direct, role));
|
|
4251
|
+
}),
|
|
4252
|
+
hasRole: (input) => Effect.suspend(() => {
|
|
4253
|
+
const now = Date.now();
|
|
4254
|
+
const [scopeType, scopeIdPresent, scopeId] = permissionScopeKey(input.scope);
|
|
4255
|
+
const allowed = queryExists(Query.select({ value: Query.literal(1) }).pipe(Query.from(tables.roleGrant.table), Query.where(queryEq(tables.roleGrant.select.subjectType, input.subject.type)), Query.where(queryEq(tables.roleGrant.select.subjectId, input.subject.id)), Query.where(queryEq(tables.roleGrant.select.roleId, input.role)), Query.where(queryIsNull(tables.roleGrant.select.revokedAt)), Query.where(queryOr(queryIsNull(tables.roleGrant.select.expiresAt), queryGt(tables.roleGrant.select.expiresAt, now))), Query.where(queryOr(queryAnd(queryEq(tables.roleGrant.select.scopeType, "global"), queryEq(tables.roleGrant.select.scopeIdPresent, 0)), queryAnd(queryEq(tables.roleGrant.select.scopeType, scopeType), queryEq(tables.roleGrant.select.scopeIdPresent, scopeIdPresent), queryEq(tables.roleGrant.select.scopeId, scopeId))))));
|
|
4256
|
+
return checkResult("has_role", allowed);
|
|
4257
|
+
}),
|
|
4258
|
+
});
|
|
4259
|
+
};
|
|
4260
|
+
export const makeEffectQbSqliteStorage = (executor, atomic, options = {}) => ({
|
|
2781
4261
|
userStore: makeEffectQbSqliteUserStore(executor, options),
|
|
4262
|
+
identityStore: makeEffectQbSqliteIdentityStore(executor, atomic, options),
|
|
4263
|
+
registrationStore: makeEffectQbSqliteRegistrationStore(executor, atomic, options),
|
|
2782
4264
|
credentialStore: makeEffectQbSqliteCredentialStore(executor, options),
|
|
2783
4265
|
sessionStore: makeEffectQbSqliteSessionStore(executor, options),
|
|
2784
4266
|
verificationStore: makeEffectQbSqliteVerificationStore(executor, options),
|
|
@@ -2788,11 +4270,16 @@ export const makeEffectQbSqliteStorage = (executor, options = {}) => ({
|
|
|
2788
4270
|
trustedDeviceStore: makeEffectQbSqliteTrustedDeviceStore(executor, options),
|
|
2789
4271
|
passkeyCredentialStore: makeEffectQbSqlitePasskeyCredentialStore(executor, options),
|
|
2790
4272
|
totpFactorStore: makeEffectQbSqliteTotpFactorStore(executor, options),
|
|
2791
|
-
|
|
4273
|
+
totpSessionRotateStore: makeEffectQbSqliteTotpSessionRotateStore(executor, atomic, options),
|
|
4274
|
+
totpLoginCommitStore: makeEffectQbSqliteTotpLoginCommitStore(atomic, options),
|
|
4275
|
+
recoveryCodeStore: makeEffectQbSqliteRecoveryCodeStore(executor, atomic, options),
|
|
4276
|
+
recoveryCodeSessionRotateStore: makeEffectQbSqliteRecoveryCodeSessionRotateStore(executor, atomic, options),
|
|
4277
|
+
recoveryCodeLoginCommitStore: makeEffectQbSqliteRecoveryCodeLoginCommitStore(atomic, options),
|
|
2792
4278
|
apiKeyStore: makeEffectQbSqliteApiKeyStore(executor, options),
|
|
2793
|
-
refreshTokenStore: makeEffectQbSqliteRefreshTokenStore(executor, options),
|
|
4279
|
+
refreshTokenStore: makeEffectQbSqliteRefreshTokenStore(executor, atomic, options),
|
|
2794
4280
|
jwtRevocationStore: makeEffectQbSqliteJwtRevocationStore(executor, options),
|
|
2795
4281
|
oauthAccountStore: makeEffectQbSqliteOAuthAccountStore(executor, options),
|
|
4282
|
+
oauthIdentityBridgeStore: makeEffectQbSqliteOAuthIdentityBridgeStore(executor, atomic, options),
|
|
2796
4283
|
oauthClientStore: makeEffectQbSqliteOAuthClientStore(executor, options),
|
|
2797
4284
|
oauthConsentStore: makeEffectQbSqliteOAuthConsentStore(executor, options),
|
|
2798
4285
|
oauthAuthorizationCodeStore: makeEffectQbSqliteOAuthAuthorizationCodeStore(executor, options),
|
|
@@ -2803,8 +4290,9 @@ export const makeEffectQbSqliteStorage = (executor, options = {}) => ({
|
|
|
2803
4290
|
securityTimelineStore: makeEffectQbSqliteSecurityTimelineStore(executor, options),
|
|
2804
4291
|
webhookOutboxStore: makeEffectQbSqliteWebhookOutboxStore(executor, options),
|
|
2805
4292
|
webhookReplayStore: makeEffectQbSqliteWebhookReplayStore(executor, options),
|
|
4293
|
+
permissionStore: makeEffectQbSqlitePermissionStore(executor, options),
|
|
2806
4294
|
});
|
|
2807
|
-
export const EffectQbSqliteAuthStorageLive = (executor, options = {}) => Layer.mergeAll(Layer.succeed(UserStore)(makeEffectQbSqliteUserStore(executor, options)), Layer.succeed(CredentialStore)(makeEffectQbSqliteCredentialStore(executor, options)), Layer.succeed(SessionStore)(makeEffectQbSqliteSessionStore(executor, options)), Layer.succeed(VerificationStore)(makeEffectQbSqliteVerificationStore(executor, options)), Layer.succeed(AuditLogStore)(makeEffectQbSqliteAuditLogStore(executor, options)), Layer.succeed(LoginApprovalReviewStore)(makeEffectQbSqliteLoginApprovalReviewStore(executor, options)), Layer.succeed(LoginRiskHistory)(makeEffectQbSqliteLoginRiskHistoryStore(executor, options)), Layer.succeed(TrustedDeviceStore)(makeEffectQbSqliteTrustedDeviceStore(executor, options)), Layer.succeed(PasskeyCredentialStore)(makeEffectQbSqlitePasskeyCredentialStore(executor, options)), Layer.succeed(TotpFactorStore)(makeEffectQbSqliteTotpFactorStore(executor, options)), Layer.succeed(RecoveryCodeStore)(makeEffectQbSqliteRecoveryCodeStore(executor, options)), Layer.succeed(ApiKeyStore)(makeEffectQbSqliteApiKeyStore(executor, options)), Layer.succeed(RefreshTokenStore)(makeEffectQbSqliteRefreshTokenStore(executor, options)), Layer.succeed(JwtRevocationStore)(makeEffectQbSqliteJwtRevocationStore(executor, options)), Layer.succeed(OAuthAccountStore)(makeEffectQbSqliteOAuthAccountStore(executor, options)), Layer.succeed(OAuthClientStore)(makeEffectQbSqliteOAuthClientStore(executor, options)), Layer.succeed(OAuthConsentStore)(makeEffectQbSqliteOAuthConsentStore(executor, options)), Layer.succeed(OAuthAuthorizationCodeStore)(makeEffectQbSqliteOAuthAuthorizationCodeStore(executor, options)), Layer.succeed(OAuthClientSecretStore)(makeEffectQbSqliteOAuthClientSecretStore(executor, options)), Layer.succeed(OAuthProviderModeTokenStore)(makeEffectQbSqliteOAuthProviderModeTokenStore(executor, options)), Layer.succeed(OAuthProviderTokenVault)(makeEffectQbSqliteOAuthProviderTokenVault(executor, options)), Layer.succeed(DomainVerificationStore)(makeEffectQbSqliteDomainVerificationStore(executor, options)), Layer.succeed(SecurityTimelineStore)(makeEffectQbSqliteSecurityTimelineStore(executor, options)), Layer.succeed(WebhookOutboxStore)(makeEffectQbSqliteWebhookOutboxStore(executor, options)), Layer.succeed(WebhookReplayStore)(makeEffectQbSqliteWebhookReplayStore(executor, options)));
|
|
4295
|
+
export const EffectQbSqliteAuthStorageLive = (executor, atomic, options = {}) => Layer.mergeAll(Layer.succeed(UserStore)(makeEffectQbSqliteUserStore(executor, options)), Layer.succeed(IdentityStore)(makeEffectQbSqliteIdentityStore(executor, atomic, options)), Layer.succeed(RegistrationStore)(makeEffectQbSqliteRegistrationStore(executor, atomic, options)), Layer.succeed(CredentialStore)(makeEffectQbSqliteCredentialStore(executor, options)), Layer.succeed(SessionStore)(makeEffectQbSqliteSessionStore(executor, options)), Layer.succeed(VerificationStore)(makeEffectQbSqliteVerificationStore(executor, options)), Layer.succeed(AuditLogStore)(makeEffectQbSqliteAuditLogStore(executor, options)), Layer.succeed(LoginApprovalReviewStore)(makeEffectQbSqliteLoginApprovalReviewStore(executor, options)), Layer.succeed(LoginRiskHistory)(makeEffectQbSqliteLoginRiskHistoryStore(executor, options)), Layer.succeed(TrustedDeviceStore)(makeEffectQbSqliteTrustedDeviceStore(executor, options)), Layer.succeed(PasskeyCredentialStore)(makeEffectQbSqlitePasskeyCredentialStore(executor, options)), Layer.succeed(TotpFactorStore)(makeEffectQbSqliteTotpFactorStore(executor, options)), Layer.succeed(TotpSessionRotateStore)(makeEffectQbSqliteTotpSessionRotateStore(executor, atomic, options)), Layer.succeed(TotpLoginCommitStore)(makeEffectQbSqliteTotpLoginCommitStore(atomic, options)), Layer.succeed(RecoveryCodeStore)(makeEffectQbSqliteRecoveryCodeStore(executor, atomic, options)), Layer.succeed(RecoveryCodeSessionRotateStore)(makeEffectQbSqliteRecoveryCodeSessionRotateStore(executor, atomic, options)), Layer.succeed(RecoveryCodeLoginCommitStore)(makeEffectQbSqliteRecoveryCodeLoginCommitStore(atomic, options)), Layer.succeed(ApiKeyStore)(makeEffectQbSqliteApiKeyStore(executor, options)), Layer.succeed(RefreshTokenStore)(makeEffectQbSqliteRefreshTokenStore(executor, atomic, options)), Layer.succeed(JwtRevocationStore)(makeEffectQbSqliteJwtRevocationStore(executor, options)), Layer.succeed(OAuthAccountStore)(makeEffectQbSqliteOAuthAccountStore(executor, options)), Layer.succeed(OAuthIdentityBridgeStore)(makeEffectQbSqliteOAuthIdentityBridgeStore(executor, atomic, options)), Layer.succeed(OAuthClientStore)(makeEffectQbSqliteOAuthClientStore(executor, options)), Layer.succeed(OAuthConsentStore)(makeEffectQbSqliteOAuthConsentStore(executor, options)), Layer.succeed(OAuthAuthorizationCodeStore)(makeEffectQbSqliteOAuthAuthorizationCodeStore(executor, options)), Layer.succeed(OAuthClientSecretStore)(makeEffectQbSqliteOAuthClientSecretStore(executor, options)), Layer.succeed(OAuthProviderModeTokenStore)(makeEffectQbSqliteOAuthProviderModeTokenStore(executor, options)), Layer.succeed(OAuthProviderTokenVault)(makeEffectQbSqliteOAuthProviderTokenVault(executor, options)), Layer.succeed(DomainVerificationStore)(makeEffectQbSqliteDomainVerificationStore(executor, options)), Layer.succeed(SecurityTimelineStore)(makeEffectQbSqliteSecurityTimelineStore(executor, options)), Layer.succeed(WebhookOutboxStore)(makeEffectQbSqliteWebhookOutboxStore(executor, options)), Layer.succeed(WebhookReplayStore)(makeEffectQbSqliteWebhookReplayStore(executor, options)), Layer.succeed(PermissionStore)(makeEffectQbSqlitePermissionStore(executor, options)));
|
|
2808
4296
|
export const makeD1EffectQbSqliteExecutor = (database) => Executor.make({
|
|
2809
4297
|
driver: Executor.driver((query) => Effect.tryPromise({
|
|
2810
4298
|
try: () => database
|
|
@@ -2821,5 +4309,110 @@ export const makeD1EffectQbSqliteExecutor = (database) => Executor.make({
|
|
|
2821
4309
|
return Effect.succeed([...(result.results ?? [])]);
|
|
2822
4310
|
}))),
|
|
2823
4311
|
});
|
|
2824
|
-
export const
|
|
4312
|
+
export const makeD1EffectQbSqliteAtomicPlanExecutor = (database) => {
|
|
4313
|
+
const execute = (plans) => {
|
|
4314
|
+
if (plans.length === 0) {
|
|
4315
|
+
return Effect.succeed([]);
|
|
4316
|
+
}
|
|
4317
|
+
return Effect.try({
|
|
4318
|
+
try: () => plans.map((plan, statement) => {
|
|
4319
|
+
try {
|
|
4320
|
+
return {
|
|
4321
|
+
plan,
|
|
4322
|
+
rendered: Renderer.sqlite.render(plan),
|
|
4323
|
+
};
|
|
4324
|
+
}
|
|
4325
|
+
catch (error) {
|
|
4326
|
+
throw new AtomicPlanError({
|
|
4327
|
+
statement,
|
|
4328
|
+
phase: "render",
|
|
4329
|
+
commitState: "not-committed",
|
|
4330
|
+
cause: error,
|
|
4331
|
+
});
|
|
4332
|
+
}
|
|
4333
|
+
}),
|
|
4334
|
+
catch: (cause) => cause instanceof AtomicPlanError
|
|
4335
|
+
? cause
|
|
4336
|
+
: new AtomicPlanError({
|
|
4337
|
+
phase: "render",
|
|
4338
|
+
commitState: "not-committed",
|
|
4339
|
+
cause,
|
|
4340
|
+
}),
|
|
4341
|
+
}).pipe(Effect.flatMap((renderedPlans) => Effect.tryPromise({
|
|
4342
|
+
try: () => {
|
|
4343
|
+
const statements = renderedPlans.map(({ rendered }, statement) => {
|
|
4344
|
+
try {
|
|
4345
|
+
return database.prepare(rendered.sql).bind(...rendered.params);
|
|
4346
|
+
}
|
|
4347
|
+
catch (error) {
|
|
4348
|
+
throw new AtomicPlanError({
|
|
4349
|
+
statement,
|
|
4350
|
+
phase: "prepare",
|
|
4351
|
+
commitState: "not-committed",
|
|
4352
|
+
cause: error,
|
|
4353
|
+
});
|
|
4354
|
+
}
|
|
4355
|
+
});
|
|
4356
|
+
return database.batch(statements);
|
|
4357
|
+
},
|
|
4358
|
+
catch: (cause) => cause instanceof AtomicPlanError
|
|
4359
|
+
? cause
|
|
4360
|
+
: new AtomicPlanError({
|
|
4361
|
+
phase: "execute",
|
|
4362
|
+
commitState: "unknown",
|
|
4363
|
+
cause,
|
|
4364
|
+
}),
|
|
4365
|
+
}).pipe(Effect.flatMap((results) => {
|
|
4366
|
+
const failed = results.findIndex((result) => result.success === false || result.error !== undefined);
|
|
4367
|
+
if (failed !== -1) {
|
|
4368
|
+
return Effect.fail(new AtomicPlanError({
|
|
4369
|
+
statement: failed,
|
|
4370
|
+
phase: "execute",
|
|
4371
|
+
commitState: "not-committed",
|
|
4372
|
+
cause: results[failed]?.error,
|
|
4373
|
+
}));
|
|
4374
|
+
}
|
|
4375
|
+
if (results.length !== renderedPlans.length) {
|
|
4376
|
+
return Effect.fail(new AtomicPlanError({
|
|
4377
|
+
phase: "execute",
|
|
4378
|
+
commitState: "unknown",
|
|
4379
|
+
cause: new Error(`D1 batch returned ${results.length} results for ${renderedPlans.length} statements`),
|
|
4380
|
+
}));
|
|
4381
|
+
}
|
|
4382
|
+
return Effect.try({
|
|
4383
|
+
try: () => results.map((result, statement) => {
|
|
4384
|
+
const entry = renderedPlans[statement];
|
|
4385
|
+
if (entry === undefined) {
|
|
4386
|
+
throw new AtomicPlanError({
|
|
4387
|
+
statement,
|
|
4388
|
+
phase: "decode",
|
|
4389
|
+
commitState: "committed",
|
|
4390
|
+
cause: new Error("Missing rendered atomic plan"),
|
|
4391
|
+
});
|
|
4392
|
+
}
|
|
4393
|
+
try {
|
|
4394
|
+
return EffectQbExecutor.decodeRows(entry.rendered, entry.plan, [...(result.results ?? [])], { driverMode: "raw" });
|
|
4395
|
+
}
|
|
4396
|
+
catch (error) {
|
|
4397
|
+
throw new AtomicPlanError({
|
|
4398
|
+
statement,
|
|
4399
|
+
phase: "decode",
|
|
4400
|
+
commitState: "committed",
|
|
4401
|
+
cause: error,
|
|
4402
|
+
});
|
|
4403
|
+
}
|
|
4404
|
+
}),
|
|
4405
|
+
catch: (cause) => cause instanceof AtomicPlanError
|
|
4406
|
+
? cause
|
|
4407
|
+
: new AtomicPlanError({
|
|
4408
|
+
phase: "decode",
|
|
4409
|
+
commitState: "committed",
|
|
4410
|
+
cause,
|
|
4411
|
+
}),
|
|
4412
|
+
});
|
|
4413
|
+
}))));
|
|
4414
|
+
};
|
|
4415
|
+
return { dialect: "sqlite", execute };
|
|
4416
|
+
};
|
|
4417
|
+
export const D1EffectQbSqliteAuthStorageLive = (database, options = {}) => EffectQbSqliteAuthStorageLive(makeD1EffectQbSqliteExecutor(database), makeD1EffectQbSqliteAtomicPlanExecutor(database), options);
|
|
2825
4418
|
//# sourceMappingURL=EffectQbSqliteStorage.js.map
|