@effect-auth/core 0.1.0-alpha.0 → 0.1.0-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/README.md +774 -36
  2. package/dist/ApiKey.d.ts +207 -0
  3. package/dist/ApiKey.d.ts.map +1 -0
  4. package/dist/ApiKey.js +275 -0
  5. package/dist/ApiKey.js.map +1 -0
  6. package/dist/AuditLog.d.ts +278 -1
  7. package/dist/AuditLog.d.ts.map +1 -1
  8. package/dist/AuditLog.js +217 -2
  9. package/dist/AuditLog.js.map +1 -1
  10. package/dist/AuthConfig.d.ts +42 -0
  11. package/dist/AuthConfig.d.ts.map +1 -0
  12. package/dist/AuthConfig.js +43 -0
  13. package/dist/AuthConfig.js.map +1 -0
  14. package/dist/AuthFlow.d.ts +465 -36
  15. package/dist/AuthFlow.d.ts.map +1 -1
  16. package/dist/AuthFlow.js +660 -5
  17. package/dist/AuthFlow.js.map +1 -1
  18. package/dist/AuthKernel.d.ts +3 -0
  19. package/dist/AuthKernel.d.ts.map +1 -0
  20. package/dist/AuthKernel.js +9 -0
  21. package/dist/AuthKernel.js.map +1 -0
  22. package/dist/Challenge.d.ts +27 -4
  23. package/dist/Challenge.d.ts.map +1 -1
  24. package/dist/Challenge.js +170 -1
  25. package/dist/Challenge.js.map +1 -1
  26. package/dist/Client.d.ts +378 -0
  27. package/dist/Client.d.ts.map +1 -0
  28. package/dist/Client.js +768 -0
  29. package/dist/Client.js.map +1 -0
  30. package/dist/Crypto.d.ts +9 -1
  31. package/dist/Crypto.d.ts.map +1 -1
  32. package/dist/Crypto.js +65 -1
  33. package/dist/Crypto.js.map +1 -1
  34. package/dist/EffectQbSqliteStorage.d.ts +600 -0
  35. package/dist/EffectQbSqliteStorage.d.ts.map +1 -0
  36. package/dist/EffectQbSqliteStorage.js +1637 -0
  37. package/dist/EffectQbSqliteStorage.js.map +1 -0
  38. package/dist/EmailOtp.d.ts +120 -0
  39. package/dist/EmailOtp.d.ts.map +1 -0
  40. package/dist/EmailOtp.js +286 -0
  41. package/dist/EmailOtp.js.map +1 -0
  42. package/dist/EmailVerification.d.ts +121 -0
  43. package/dist/EmailVerification.d.ts.map +1 -0
  44. package/dist/EmailVerification.js +200 -0
  45. package/dist/EmailVerification.js.map +1 -0
  46. package/dist/HttpApi/Api.d.ts +1956 -0
  47. package/dist/HttpApi/Api.d.ts.map +1 -0
  48. package/dist/HttpApi/Api.js +2325 -0
  49. package/dist/HttpApi/Api.js.map +1 -0
  50. package/dist/HttpApi/Endpoints.d.ts +945 -0
  51. package/dist/HttpApi/Endpoints.d.ts.map +1 -0
  52. package/dist/HttpApi/Endpoints.js +373 -0
  53. package/dist/HttpApi/Endpoints.js.map +1 -0
  54. package/dist/HttpApi/Errors.d.ts +82 -0
  55. package/dist/HttpApi/Errors.d.ts.map +1 -0
  56. package/dist/HttpApi/Errors.js +184 -0
  57. package/dist/HttpApi/Errors.js.map +1 -0
  58. package/dist/HttpApi/Schemas.d.ts +1113 -0
  59. package/dist/HttpApi/Schemas.d.ts.map +1 -0
  60. package/dist/HttpApi/Schemas.js +734 -0
  61. package/dist/HttpApi/Schemas.js.map +1 -0
  62. package/dist/HttpApi/Security.d.ts +159 -0
  63. package/dist/HttpApi/Security.d.ts.map +1 -0
  64. package/dist/HttpApi/Security.js +530 -0
  65. package/dist/HttpApi/Security.js.map +1 -0
  66. package/dist/HttpApi/Transport.d.ts +65 -0
  67. package/dist/HttpApi/Transport.d.ts.map +1 -0
  68. package/dist/HttpApi/Transport.js +311 -0
  69. package/dist/HttpApi/Transport.js.map +1 -0
  70. package/dist/HttpApi/index.d.ts +8 -0
  71. package/dist/HttpApi/index.d.ts.map +1 -0
  72. package/dist/HttpApi/index.js +7 -0
  73. package/dist/HttpApi/index.js.map +1 -0
  74. package/dist/HttpApi.d.ts +2 -0
  75. package/dist/HttpApi.d.ts.map +1 -0
  76. package/dist/HttpApi.js +2 -0
  77. package/dist/HttpApi.js.map +1 -0
  78. package/dist/Identifiers.d.ts.map +1 -1
  79. package/dist/Identifiers.js.map +1 -1
  80. package/dist/Internal.d.ts +7 -0
  81. package/dist/Internal.d.ts.map +1 -0
  82. package/dist/Internal.js +12 -0
  83. package/dist/Internal.js.map +1 -0
  84. package/dist/Jwt.d.ts +271 -0
  85. package/dist/Jwt.d.ts.map +1 -0
  86. package/dist/Jwt.js +512 -0
  87. package/dist/Jwt.js.map +1 -0
  88. package/dist/LoginApproval.d.ts +314 -0
  89. package/dist/LoginApproval.d.ts.map +1 -0
  90. package/dist/LoginApproval.js +551 -0
  91. package/dist/LoginApproval.js.map +1 -0
  92. package/dist/LoginNotification.d.ts +183 -0
  93. package/dist/LoginNotification.d.ts.map +1 -0
  94. package/dist/LoginNotification.js +329 -0
  95. package/dist/LoginNotification.js.map +1 -0
  96. package/dist/LoginRisk.d.ts +73 -0
  97. package/dist/LoginRisk.d.ts.map +1 -0
  98. package/dist/LoginRisk.js +57 -0
  99. package/dist/LoginRisk.js.map +1 -0
  100. package/dist/MagicLink.d.ts +85 -0
  101. package/dist/MagicLink.d.ts.map +1 -0
  102. package/dist/MagicLink.js +206 -0
  103. package/dist/MagicLink.js.map +1 -0
  104. package/dist/Mailer.d.ts +147 -0
  105. package/dist/Mailer.d.ts.map +1 -0
  106. package/dist/Mailer.js +208 -0
  107. package/dist/Mailer.js.map +1 -0
  108. package/dist/OAuth.d.ts +934 -0
  109. package/dist/OAuth.d.ts.map +1 -0
  110. package/dist/OAuth.js +1661 -0
  111. package/dist/OAuth.js.map +1 -0
  112. package/dist/Passkey.d.ts +334 -0
  113. package/dist/Passkey.d.ts.map +1 -0
  114. package/dist/Passkey.js +422 -0
  115. package/dist/Passkey.js.map +1 -0
  116. package/dist/PasskeySimpleWebAuthn.d.ts +11 -0
  117. package/dist/PasskeySimpleWebAuthn.d.ts.map +1 -0
  118. package/dist/PasskeySimpleWebAuthn.js +181 -0
  119. package/dist/PasskeySimpleWebAuthn.js.map +1 -0
  120. package/dist/Password.d.ts +237 -0
  121. package/dist/Password.d.ts.map +1 -0
  122. package/dist/Password.js +641 -0
  123. package/dist/Password.js.map +1 -0
  124. package/dist/Policy.d.ts +7 -4
  125. package/dist/Policy.d.ts.map +1 -1
  126. package/dist/Policy.js +28 -5
  127. package/dist/Policy.js.map +1 -1
  128. package/dist/Privacy.d.ts +18 -0
  129. package/dist/Privacy.d.ts.map +1 -1
  130. package/dist/Privacy.js +63 -3
  131. package/dist/Privacy.js.map +1 -1
  132. package/dist/RateLimiter.d.ts +104 -0
  133. package/dist/RateLimiter.d.ts.map +1 -0
  134. package/dist/RateLimiter.js +118 -0
  135. package/dist/RateLimiter.js.map +1 -0
  136. package/dist/RecoveryCode.d.ts +169 -0
  137. package/dist/RecoveryCode.d.ts.map +1 -0
  138. package/dist/RecoveryCode.js +299 -0
  139. package/dist/RecoveryCode.js.map +1 -0
  140. package/dist/RefreshToken.d.ts +222 -0
  141. package/dist/RefreshToken.d.ts.map +1 -0
  142. package/dist/RefreshToken.js +342 -0
  143. package/dist/RefreshToken.js.map +1 -0
  144. package/dist/SecurityTimeline.d.ts +72 -0
  145. package/dist/SecurityTimeline.d.ts.map +1 -0
  146. package/dist/SecurityTimeline.js +386 -0
  147. package/dist/SecurityTimeline.js.map +1 -0
  148. package/dist/Sessions.d.ts +135 -11
  149. package/dist/Sessions.d.ts.map +1 -1
  150. package/dist/Sessions.js +546 -1
  151. package/dist/Sessions.js.map +1 -1
  152. package/dist/StepUp.d.ts +44 -0
  153. package/dist/StepUp.d.ts.map +1 -0
  154. package/dist/StepUp.js +82 -0
  155. package/dist/StepUp.js.map +1 -0
  156. package/dist/Storage.d.ts +175 -0
  157. package/dist/Storage.d.ts.map +1 -0
  158. package/dist/Storage.js +27 -0
  159. package/dist/Storage.js.map +1 -0
  160. package/dist/StorageMigrations.d.ts +19 -0
  161. package/dist/StorageMigrations.d.ts.map +1 -0
  162. package/dist/StorageMigrations.js +291 -0
  163. package/dist/StorageMigrations.js.map +1 -0
  164. package/dist/Testing.d.ts +9 -1
  165. package/dist/Testing.d.ts.map +1 -1
  166. package/dist/Testing.js +30 -2
  167. package/dist/Testing.js.map +1 -1
  168. package/dist/Totp.d.ts +220 -0
  169. package/dist/Totp.d.ts.map +1 -0
  170. package/dist/Totp.js +477 -0
  171. package/dist/Totp.js.map +1 -0
  172. package/dist/TrustedDevice.d.ts +127 -0
  173. package/dist/TrustedDevice.d.ts.map +1 -0
  174. package/dist/TrustedDevice.js +252 -0
  175. package/dist/TrustedDevice.js.map +1 -0
  176. package/dist/WaitUntil.d.ts +3 -0
  177. package/dist/WaitUntil.d.ts.map +1 -1
  178. package/dist/WaitUntil.js +6 -3
  179. package/dist/WaitUntil.js.map +1 -1
  180. package/dist/Webhook.d.ts +72 -0
  181. package/dist/Webhook.d.ts.map +1 -0
  182. package/dist/Webhook.js +82 -0
  183. package/dist/Webhook.js.map +1 -0
  184. package/dist/alchemy/cloudflare/Email.d.ts +25 -0
  185. package/dist/alchemy/cloudflare/Email.d.ts.map +1 -0
  186. package/dist/alchemy/cloudflare/Email.js +117 -0
  187. package/dist/alchemy/cloudflare/Email.js.map +1 -0
  188. package/dist/alchemy/cloudflare/RateLimitDurableObject.d.ts +34 -0
  189. package/dist/alchemy/cloudflare/RateLimitDurableObject.d.ts.map +1 -0
  190. package/dist/alchemy/cloudflare/RateLimitDurableObject.js +30 -0
  191. package/dist/alchemy/cloudflare/RateLimitDurableObject.js.map +1 -0
  192. package/dist/cloudflare/Email.d.ts +25 -0
  193. package/dist/cloudflare/Email.d.ts.map +1 -0
  194. package/dist/cloudflare/Email.js +153 -0
  195. package/dist/cloudflare/Email.js.map +1 -0
  196. package/dist/cloudflare/RateLimitDurableObject.d.ts +50 -0
  197. package/dist/cloudflare/RateLimitDurableObject.d.ts.map +1 -0
  198. package/dist/cloudflare/RateLimitDurableObject.js +51 -0
  199. package/dist/cloudflare/RateLimitDurableObject.js.map +1 -0
  200. package/dist/index.d.ts +22 -0
  201. package/dist/index.d.ts.map +1 -1
  202. package/dist/index.js +22 -0
  203. package/dist/index.js.map +1 -1
  204. package/dist/internal/RateLimitDurableObject.d.ts +32 -0
  205. package/dist/internal/RateLimitDurableObject.d.ts.map +1 -0
  206. package/dist/internal/RateLimitDurableObject.js +80 -0
  207. package/dist/internal/RateLimitDurableObject.js.map +1 -0
  208. package/migrations/0001_auth_storage.sql +32 -0
  209. package/migrations/0002_auth_password.sql +25 -0
  210. package/migrations/0003_auth_login_approval_review.sql +22 -0
  211. package/migrations/0004_auth_trusted_device.sql +12 -0
  212. package/migrations/0005_auth_passkey.sql +17 -0
  213. package/migrations/0006_auth_totp_factor.sql +17 -0
  214. package/migrations/0007_auth_recovery_code.sql +13 -0
  215. package/migrations/0008_auth_api_key.sql +17 -0
  216. package/migrations/0009_auth_refresh_token.sql +21 -0
  217. package/migrations/0010_auth_jwt_revocation.sql +8 -0
  218. package/migrations/0011_auth_oauth_account.sql +16 -0
  219. package/migrations/0012_auth_oauth_provider_token_vault.sql +20 -0
  220. package/migrations/0013_auth_security_timeline.sql +16 -0
  221. package/package.json +161 -16
  222. package/LICENSE +0 -21
package/dist/OAuth.js ADDED
@@ -0,0 +1,1661 @@
1
+ import { Brand, Context, Data, Duration, Effect, Encoding, Layer, Option, Redacted, } from "effect";
2
+ import { Challenge } from "./Challenge.js";
3
+ import { Crypto } from "./Crypto.js";
4
+ import { ChallengeId, Email, OAuthAccountId, OAuthProviderId, UnixMillis, UserId, } from "./Identifiers.js";
5
+ import { currentUnixMillis } from "./Internal.js";
6
+ import { JwtAlgorithms, JwtError, JwtKeyId, JwtKeys, JwtVerifier, } from "./Jwt.js";
7
+ export const OAuthStateToken = Brand.nominal();
8
+ export const OAuthCodeVerifier = Brand.nominal();
9
+ export const OAuthCodeChallenge = Brand.nominal();
10
+ export const OAuthNonce = Brand.nominal();
11
+ export const OAuthAuthorizationCode = Brand.nominal();
12
+ export const OAuthAccessToken = Brand.nominal();
13
+ export const OAuthRefreshToken = Brand.nominal();
14
+ export const OAuthIdToken = Brand.nominal();
15
+ export const defaultOAuthStateTtl = Duration.minutes(10);
16
+ export const defaultOAuthLinkConfirmationTtl = Duration.minutes(15);
17
+ export const defaultOAuthJwksCacheTtl = Duration.minutes(10);
18
+ export const defaultOAuthStateSecretBytes = 32;
19
+ export const defaultOAuthCodeVerifierBytes = 32;
20
+ export const defaultOAuthLinkConfirmationSecretBytes = 32;
21
+ export const defaultOAuthNonceBytes = 16;
22
+ export const appleSignInProviderId = OAuthProviderId("apple");
23
+ export const appleSignInIssuer = "https://appleid.apple.com";
24
+ export const appleSignInAuthorizationEndpoint = "https://appleid.apple.com/auth/authorize";
25
+ export const appleSignInTokenEndpoint = "https://appleid.apple.com/auth/token";
26
+ export const appleSignInRevocationEndpoint = "https://appleid.apple.com/auth/revoke";
27
+ export const appleSignInJwksUri = "https://appleid.apple.com/auth/keys";
28
+ export const appleSignInPrivateRelayDomain = "privaterelay.appleid.com";
29
+ export const appleSignInDefaultScopes = ["openid", "email", "name"];
30
+ export const appleSignInDefaultAuthorizationParams = {
31
+ response_mode: "form_post",
32
+ };
33
+ export const githubOAuthProviderId = OAuthProviderId("github");
34
+ export const githubOAuthAuthorizationEndpoint = "https://github.com/login/oauth/authorize";
35
+ export const githubOAuthTokenEndpoint = "https://github.com/login/oauth/access_token";
36
+ export const githubOAuthUserEndpoint = "https://api.github.com/user";
37
+ export const githubOAuthEmailsEndpoint = "https://api.github.com/user/emails";
38
+ export const githubOAuthDefaultScopes = ["read:user", "user:email"];
39
+ export const githubOAuthApiVersion = "2022-11-28";
40
+ export const githubOAuthApiVersionHeader = {
41
+ "x-github-api-version": githubOAuthApiVersion,
42
+ };
43
+ export const googleOidcProviderId = OAuthProviderId("google");
44
+ export const googleOidcIssuer = "https://accounts.google.com";
45
+ export const googleOidcAuthorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth";
46
+ export const googleOidcTokenEndpoint = "https://oauth2.googleapis.com/token";
47
+ export const googleOidcRevocationEndpoint = "https://oauth2.googleapis.com/revoke";
48
+ export const googleOidcUserInfoEndpoint = "https://openidconnect.googleapis.com/v1/userinfo";
49
+ export const googleOidcJwksUri = "https://www.googleapis.com/oauth2/v3/certs";
50
+ export const googleOidcDefaultScopes = ["openid", "email", "profile"];
51
+ export const googleOidcOfflineAccessAuthorizationParams = {
52
+ access_type: "offline",
53
+ prompt: "consent",
54
+ };
55
+ export const microsoftOidcProviderId = OAuthProviderId("microsoft");
56
+ export const microsoftOidcLoginBaseUrl = "https://login.microsoftonline.com";
57
+ export const microsoftOidcUserInfoEndpoint = "https://graph.microsoft.com/oidc/userinfo";
58
+ export const microsoftOidcDefaultScopes = ["openid", "email", "profile"];
59
+ export const microsoftOidcOfflineAccessScopes = [
60
+ "openid",
61
+ "email",
62
+ "profile",
63
+ "offline_access",
64
+ ];
65
+ export const genericOidcDefaultScopes = ["openid", "email", "profile"];
66
+ const microsoftOidcTenantPathSegment = (tenant) => encodeURIComponent(tenant.trim());
67
+ export const microsoftOidcAuthorizationEndpoint = (tenant) => `${microsoftOidcLoginBaseUrl}/${microsoftOidcTenantPathSegment(tenant)}/oauth2/v2.0/authorize`;
68
+ export const microsoftOidcTokenEndpoint = (tenant) => `${microsoftOidcLoginBaseUrl}/${microsoftOidcTenantPathSegment(tenant)}/oauth2/v2.0/token`;
69
+ export const microsoftOidcJwksUri = (tenant) => `${microsoftOidcLoginBaseUrl}/${microsoftOidcTenantPathSegment(tenant)}/discovery/v2.0/keys`;
70
+ export const microsoftOidcIssuer = (tenant) => `${microsoftOidcLoginBaseUrl}/${microsoftOidcTenantPathSegment(tenant)}/v2.0`;
71
+ export class OAuthProviderError extends Data.TaggedError("OAuthProviderError") {
72
+ }
73
+ export class OAuthUserInfoError extends Data.TaggedError("OAuthUserInfoError") {
74
+ }
75
+ export class OAuthProviderTokenVaultError extends Data.TaggedError("OAuthProviderTokenVaultError") {
76
+ }
77
+ export class OAuthProviderTokenVault extends Context.Service()("auth/OAuthProviderTokenVault") {
78
+ }
79
+ (function (OAuthProviderTokenVault) {
80
+ OAuthProviderTokenVault.make = (service) => OAuthProviderTokenVault.of(service);
81
+ })(OAuthProviderTokenVault || (OAuthProviderTokenVault = {}));
82
+ export class OAuthProviderTokenLifecycleError extends Data.TaggedError("OAuthProviderTokenLifecycleError") {
83
+ }
84
+ export class OAuthProviderTokenLifecycle extends Context.Service()("auth/OAuthProviderTokenLifecycle") {
85
+ }
86
+ (function (OAuthProviderTokenLifecycle) {
87
+ OAuthProviderTokenLifecycle.make = (service) => OAuthProviderTokenLifecycle.of(service);
88
+ })(OAuthProviderTokenLifecycle || (OAuthProviderTokenLifecycle = {}));
89
+ export class OidcIdTokenVerifierError extends Data.TaggedError("OidcIdTokenVerifierError") {
90
+ }
91
+ export class OidcIdTokenVerifier extends Context.Service()("auth/OidcIdTokenVerifier") {
92
+ }
93
+ (function (OidcIdTokenVerifier) {
94
+ OidcIdTokenVerifier.make = (service) => OidcIdTokenVerifier.of(service);
95
+ })(OidcIdTokenVerifier || (OidcIdTokenVerifier = {}));
96
+ export class OAuthProfileNormalizationError extends Data.TaggedError("OAuthProfileNormalizationError") {
97
+ }
98
+ export class OAuthProfileNormalizer extends Context.Service()("auth/OAuthProfileNormalizer") {
99
+ }
100
+ (function (OAuthProfileNormalizer) {
101
+ OAuthProfileNormalizer.make = (service) => OAuthProfileNormalizer.of(service);
102
+ })(OAuthProfileNormalizer || (OAuthProfileNormalizer = {}));
103
+ export class OAuthTokenExchangeError extends Data.TaggedError("OAuthTokenExchangeError") {
104
+ }
105
+ export class OAuthTokenExchange extends Context.Service()("auth/OAuthTokenExchange") {
106
+ }
107
+ (function (OAuthTokenExchange) {
108
+ OAuthTokenExchange.make = (service) => OAuthTokenExchange.of(service);
109
+ })(OAuthTokenExchange || (OAuthTokenExchange = {}));
110
+ export class OAuthProviders extends Context.Service()("auth/OAuthProviders") {
111
+ }
112
+ (function (OAuthProviders) {
113
+ OAuthProviders.make = (service) => OAuthProviders.of(service);
114
+ })(OAuthProviders || (OAuthProviders = {}));
115
+ export class OAuthStateError extends Data.TaggedError("OAuthStateError") {
116
+ }
117
+ export class OAuthState extends Context.Service()("auth/OAuthState") {
118
+ }
119
+ (function (OAuthState) {
120
+ OAuthState.make = (service) => OAuthState.of(service);
121
+ })(OAuthState || (OAuthState = {}));
122
+ export class OAuthAccountStoreError extends Data.TaggedError("OAuthAccountStoreError") {
123
+ }
124
+ export class OAuthAccountStore extends Context.Service()("auth/OAuthAccountStore") {
125
+ }
126
+ (function (OAuthAccountStore) {
127
+ OAuthAccountStore.make = (service) => OAuthAccountStore.of(service);
128
+ })(OAuthAccountStore || (OAuthAccountStore = {}));
129
+ export class OAuthAccountLinkingError extends Data.TaggedError("OAuthAccountLinkingError") {
130
+ }
131
+ export class OAuthAccountLinking extends Context.Service()("auth/OAuthAccountLinking") {
132
+ }
133
+ (function (OAuthAccountLinking) {
134
+ OAuthAccountLinking.make = (service) => OAuthAccountLinking.of(service);
135
+ })(OAuthAccountLinking || (OAuthAccountLinking = {}));
136
+ export class OAuthAccountUnlinkingError extends Data.TaggedError("OAuthAccountUnlinkingError") {
137
+ }
138
+ export class OAuthAccountUnlinking extends Context.Service()("auth/OAuthAccountUnlinking") {
139
+ }
140
+ (function (OAuthAccountUnlinking) {
141
+ OAuthAccountUnlinking.make = (service) => OAuthAccountUnlinking.of(service);
142
+ })(OAuthAccountUnlinking || (OAuthAccountUnlinking = {}));
143
+ export class OAuthLinkConfirmationError extends Data.TaggedError("OAuthLinkConfirmationError") {
144
+ }
145
+ export class OAuthLinkConfirmation extends Context.Service()("auth/OAuthLinkConfirmation") {
146
+ }
147
+ (function (OAuthLinkConfirmation) {
148
+ OAuthLinkConfirmation.make = (service) => OAuthLinkConfirmation.of(service);
149
+ })(OAuthLinkConfirmation || (OAuthLinkConfirmation = {}));
150
+ const oauthStateError = (operation, message, cause) => new OAuthStateError({ operation, message, cause });
151
+ const oauthTokenExchangeError = (operation, message, cause) => new OAuthTokenExchangeError({ operation, message, cause });
152
+ const oauthProviderTokenLifecycleError = (operation, message, cause) => new OAuthProviderTokenLifecycleError({ operation, message, cause });
153
+ const oidcIdTokenVerifierError = (operation, message, cause) => new OidcIdTokenVerifierError({ operation, message, cause });
154
+ const oauthAccountLinkingError = (operation, message, cause) => new OAuthAccountLinkingError({ operation, message, cause });
155
+ const oauthAccountUnlinkingError = (operation, message, cause) => new OAuthAccountUnlinkingError({ operation, message, cause });
156
+ const oauthLinkConfirmationError = (operation, message, cause) => new OAuthLinkConfirmationError({ operation, message, cause });
157
+ const oauthProfileNormalizationError = (reason, message, cause) => new OAuthProfileNormalizationError({
158
+ operation: "normalize",
159
+ reason,
160
+ message,
161
+ cause,
162
+ });
163
+ const oauthJwksJwtError = (message, cause) => new JwtError({
164
+ operation: "keys",
165
+ message,
166
+ ...(cause === undefined ? {} : { cause }),
167
+ });
168
+ const oauthUserInfoError = (operation, message, cause) => new OAuthUserInfoError({
169
+ operation,
170
+ message,
171
+ ...(cause === undefined ? {} : { cause }),
172
+ });
173
+ const textEncoder = new TextEncoder();
174
+ const oauthJwksPrivateMembers = new Set([
175
+ "d",
176
+ "p",
177
+ "q",
178
+ "dp",
179
+ "dq",
180
+ "qi",
181
+ "oth",
182
+ "k",
183
+ ]);
184
+ const validateByteLength = (value, operation, label) => Number.isInteger(value) && value >= 16 && value <= 128
185
+ ? Effect.succeed(value)
186
+ : Effect.fail(oauthStateError(operation, `${label} byte length must be an integer between 16 and 128`));
187
+ const validateOAuthLinkConfirmationSecretBytes = (value) => Number.isInteger(value) && value >= 16 && value <= 128
188
+ ? Effect.succeed(value)
189
+ : Effect.fail(oauthLinkConfirmationError("start", "OAuth link confirmation secret byte length must be an integer between 16 and 128"));
190
+ const metadataValue = (value) => value === undefined ? undefined : structuredClone(value);
191
+ export const makeAppleSignInProvider = (input) => ({
192
+ id: input.id ?? appleSignInProviderId,
193
+ clientId: input.clientId,
194
+ authorizationEndpoint: appleSignInAuthorizationEndpoint,
195
+ tokenEndpoint: appleSignInTokenEndpoint,
196
+ revocationEndpoint: appleSignInRevocationEndpoint,
197
+ jwksUri: appleSignInJwksUri,
198
+ issuer: appleSignInIssuer,
199
+ redirectUri: input.redirectUri,
200
+ scopes: [...(input.scopes ?? appleSignInDefaultScopes)],
201
+ authorizationParams: {
202
+ ...appleSignInDefaultAuthorizationParams,
203
+ ...input.authorizationParams,
204
+ },
205
+ ...(input.metadata === undefined ? {} : { metadata: metadataValue(input.metadata) }),
206
+ });
207
+ export const makeGithubOAuthProvider = (input) => ({
208
+ id: input.id ?? githubOAuthProviderId,
209
+ clientId: input.clientId,
210
+ authorizationEndpoint: githubOAuthAuthorizationEndpoint,
211
+ tokenEndpoint: githubOAuthTokenEndpoint,
212
+ userInfoEndpoint: githubOAuthUserEndpoint,
213
+ redirectUri: input.redirectUri,
214
+ scopes: [...(input.scopes ?? githubOAuthDefaultScopes)],
215
+ ...(input.authorizationParams === undefined
216
+ ? {}
217
+ : { authorizationParams: { ...input.authorizationParams } }),
218
+ ...(input.metadata === undefined ? {} : { metadata: metadataValue(input.metadata) }),
219
+ });
220
+ export const makeGoogleOidcProvider = (input) => ({
221
+ id: input.id ?? googleOidcProviderId,
222
+ clientId: input.clientId,
223
+ authorizationEndpoint: googleOidcAuthorizationEndpoint,
224
+ tokenEndpoint: googleOidcTokenEndpoint,
225
+ revocationEndpoint: googleOidcRevocationEndpoint,
226
+ userInfoEndpoint: googleOidcUserInfoEndpoint,
227
+ jwksUri: googleOidcJwksUri,
228
+ issuer: googleOidcIssuer,
229
+ redirectUri: input.redirectUri,
230
+ scopes: [...(input.scopes ?? googleOidcDefaultScopes)],
231
+ ...(input.authorizationParams === undefined
232
+ ? {}
233
+ : { authorizationParams: { ...input.authorizationParams } }),
234
+ ...(input.metadata === undefined ? {} : { metadata: metadataValue(input.metadata) }),
235
+ });
236
+ export const makeMicrosoftOidcProvider = (input) => ({
237
+ id: input.id ?? microsoftOidcProviderId,
238
+ clientId: input.clientId,
239
+ authorizationEndpoint: microsoftOidcAuthorizationEndpoint(input.tenant),
240
+ tokenEndpoint: microsoftOidcTokenEndpoint(input.tenant),
241
+ userInfoEndpoint: microsoftOidcUserInfoEndpoint,
242
+ jwksUri: microsoftOidcJwksUri(input.tenant),
243
+ issuer: microsoftOidcIssuer(input.tenant),
244
+ redirectUri: input.redirectUri,
245
+ scopes: [...(input.scopes ?? microsoftOidcDefaultScopes)],
246
+ ...(input.authorizationParams === undefined
247
+ ? {}
248
+ : { authorizationParams: { ...input.authorizationParams } }),
249
+ ...(input.metadata === undefined ? {} : { metadata: metadataValue(input.metadata) }),
250
+ });
251
+ export const makeGenericOidcProvider = (input) => ({
252
+ id: input.id,
253
+ clientId: input.clientId,
254
+ authorizationEndpoint: input.authorizationEndpoint,
255
+ tokenEndpoint: input.tokenEndpoint,
256
+ issuer: input.issuer,
257
+ redirectUri: input.redirectUri,
258
+ scopes: [...(input.scopes ?? genericOidcDefaultScopes)],
259
+ ...(input.userInfoEndpoint === undefined
260
+ ? {}
261
+ : { userInfoEndpoint: input.userInfoEndpoint }),
262
+ ...(input.jwksUri === undefined ? {} : { jwksUri: input.jwksUri }),
263
+ ...(input.revocationEndpoint === undefined
264
+ ? {}
265
+ : { revocationEndpoint: input.revocationEndpoint }),
266
+ ...(input.authorizationParams === undefined
267
+ ? {}
268
+ : { authorizationParams: { ...input.authorizationParams } }),
269
+ ...(input.metadata === undefined ? {} : { metadata: metadataValue(input.metadata) }),
270
+ });
271
+ const oauthClientSecretPost = (clientSecret) => ({
272
+ method: "client_secret_post",
273
+ clientSecret,
274
+ });
275
+ export const makeGoogleOidcProviderTokenRefreshInput = (input) => ({
276
+ accountId: input.accountId,
277
+ userId: input.userId,
278
+ providerId: input.provider.id,
279
+ providerAccountId: input.providerAccountId,
280
+ provider: input.provider,
281
+ scopes: [...(input.scopes ?? googleOidcDefaultScopes)],
282
+ ...(input.now === undefined ? {} : { now: input.now }),
283
+ ...(input.clientSecret === undefined
284
+ ? {}
285
+ : { clientAuthentication: oauthClientSecretPost(input.clientSecret) }),
286
+ ...(input.params === undefined ? {} : { params: input.params }),
287
+ });
288
+ export const makeGoogleOidcProviderTokenRevokeInput = (input) => ({
289
+ accountId: input.accountId,
290
+ provider: input.provider,
291
+ reason: input.reason,
292
+ ...(input.now === undefined ? {} : { now: input.now }),
293
+ ...(input.clientSecret === undefined
294
+ ? {}
295
+ : { clientAuthentication: oauthClientSecretPost(input.clientSecret) }),
296
+ ...(input.params === undefined ? {} : { params: input.params }),
297
+ });
298
+ export const makeMicrosoftOidcProviderTokenRefreshInput = (input) => ({
299
+ accountId: input.accountId,
300
+ userId: input.userId,
301
+ providerId: input.provider.id,
302
+ providerAccountId: input.providerAccountId,
303
+ provider: input.provider,
304
+ scopes: [...(input.scopes ?? microsoftOidcOfflineAccessScopes)],
305
+ ...(input.now === undefined ? {} : { now: input.now }),
306
+ ...(input.clientSecret === undefined
307
+ ? {}
308
+ : { clientAuthentication: oauthClientSecretPost(input.clientSecret) }),
309
+ ...(input.params === undefined ? {} : { params: input.params }),
310
+ });
311
+ export const makeAppleSignInProviderTokenRefreshInput = (input) => ({
312
+ accountId: input.accountId,
313
+ userId: input.userId,
314
+ providerId: input.provider.id,
315
+ providerAccountId: input.providerAccountId,
316
+ provider: input.provider,
317
+ clientAuthentication: oauthClientSecretPost(input.clientSecret),
318
+ ...(input.scopes === undefined ? {} : { scopes: input.scopes }),
319
+ ...(input.now === undefined ? {} : { now: input.now }),
320
+ ...(input.params === undefined ? {} : { params: input.params }),
321
+ });
322
+ export const makeAppleSignInProviderTokenRevokeInput = (input) => ({
323
+ accountId: input.accountId,
324
+ provider: input.provider,
325
+ reason: input.reason,
326
+ clientAuthentication: oauthClientSecretPost(input.clientSecret),
327
+ ...(input.now === undefined ? {} : { now: input.now }),
328
+ ...(input.params === undefined ? {} : { params: input.params }),
329
+ });
330
+ const cloneProviderConfig = (provider) => ({
331
+ ...provider,
332
+ ...(provider.scopes === undefined ? {} : { scopes: [...provider.scopes] }),
333
+ ...(provider.authorizationParams === undefined
334
+ ? {}
335
+ : { authorizationParams: { ...provider.authorizationParams } }),
336
+ ...(provider.metadata === undefined ? {} : { metadata: metadataValue(provider.metadata) }),
337
+ });
338
+ const encodeOAuthState = (input) => OAuthStateToken(`${input.challengeId}.${Redacted.value(input.secret)}`);
339
+ export const parseOAuthState = (state) => {
340
+ const parts = String(state).split(".");
341
+ return parts.length === 2 && parts[0] !== "" && parts[1] !== ""
342
+ ? Effect.succeed({
343
+ challengeId: ChallengeId(parts[0]),
344
+ secret: Redacted.make(parts[1]),
345
+ })
346
+ : Effect.fail(oauthStateError("parse", "OAuth state must use '<id>.<secret>' format"));
347
+ };
348
+ const stringArrayFromUnknown = (value) => Array.isArray(value) && value.every((item) => typeof item === "string")
349
+ ? value
350
+ : [];
351
+ const stringFromUnknown = (value) => typeof value === "string" && value.length > 0 ? value : undefined;
352
+ const nonEmptyStringFromUnknown = (value) => typeof value === "string" && value.trim() !== "" ? value : undefined;
353
+ export const githubOAuthVerifiedEmail = (emails) => {
354
+ for (const email of emails) {
355
+ if (email.primary === true && email.verified === true) {
356
+ const value = nonEmptyStringFromUnknown(email.email);
357
+ if (value !== undefined) {
358
+ return value;
359
+ }
360
+ }
361
+ }
362
+ for (const email of emails) {
363
+ if (email.verified === true) {
364
+ const value = nonEmptyStringFromUnknown(email.email);
365
+ if (value !== undefined) {
366
+ return value;
367
+ }
368
+ }
369
+ }
370
+ return undefined;
371
+ };
372
+ export const googleOidcHostedDomain = (value) => nonEmptyStringFromUnknown(value.hd);
373
+ export const microsoftOidcTenantId = (value) => nonEmptyStringFromUnknown(value.tid);
374
+ export const microsoftOidcObjectId = (value) => nonEmptyStringFromUnknown(value.oid);
375
+ export const microsoftOidcAccountId = (value) => {
376
+ const tenantId = microsoftOidcTenantId(value);
377
+ const objectId = microsoftOidcObjectId(value);
378
+ return tenantId === undefined || objectId === undefined
379
+ ? undefined
380
+ : `${tenantId}:${objectId}`;
381
+ };
382
+ const optionalRecord = (value) => (Object.keys(value).length === 0 ? undefined : value);
383
+ const recordFromUnknown = (value) => typeof value === "object" && value !== null && !Array.isArray(value)
384
+ ? value
385
+ : undefined;
386
+ const isJwtAlgorithm = (value) => typeof value === "string" && JwtAlgorithms.includes(value);
387
+ const isPublicJwksAlgorithm = (value) => isJwtAlgorithm(value) && value !== "HS256";
388
+ const booleanFromUnknown = (value) => typeof value === "boolean" ? value : undefined;
389
+ const emailFromUnknown = (value) => {
390
+ const email = nonEmptyStringFromUnknown(value);
391
+ return email === undefined ? undefined : Email(email);
392
+ };
393
+ const appleSignInBooleanClaim = (value) => {
394
+ if (typeof value === "boolean") {
395
+ return value;
396
+ }
397
+ if (value === "true") {
398
+ return true;
399
+ }
400
+ if (value === "false") {
401
+ return false;
402
+ }
403
+ return undefined;
404
+ };
405
+ export const decodeAppleSignInAuthorizationUser = (value) => {
406
+ const decoded = typeof value === "string"
407
+ ? (() => {
408
+ try {
409
+ return JSON.parse(value);
410
+ }
411
+ catch {
412
+ return undefined;
413
+ }
414
+ })()
415
+ : value;
416
+ const record = recordFromUnknown(decoded);
417
+ if (record === undefined) {
418
+ return undefined;
419
+ }
420
+ const nameRecord = recordFromUnknown(record.name);
421
+ const firstName = nonEmptyStringFromUnknown(nameRecord?.firstName);
422
+ const lastName = nonEmptyStringFromUnknown(nameRecord?.lastName);
423
+ const name = firstName === undefined && lastName === undefined
424
+ ? undefined
425
+ : {
426
+ ...(firstName === undefined ? {} : { firstName }),
427
+ ...(lastName === undefined ? {} : { lastName }),
428
+ };
429
+ const email = nonEmptyStringFromUnknown(record.email);
430
+ return email === undefined && name === undefined
431
+ ? undefined
432
+ : {
433
+ ...(email === undefined ? {} : { email }),
434
+ ...(name === undefined ? {} : { name }),
435
+ };
436
+ };
437
+ export const appleSignInAuthorizationUserFullName = (user) => {
438
+ const firstName = nonEmptyStringFromUnknown(user?.name?.firstName);
439
+ const lastName = nonEmptyStringFromUnknown(user?.name?.lastName);
440
+ const parts = [firstName, lastName].filter((part) => part !== undefined);
441
+ return parts.length === 0 ? undefined : parts.join(" ");
442
+ };
443
+ export const appleSignInAuthorizationUserEmail = (user) => emailFromUnknown(user?.email);
444
+ export const appleSignInIsPrivateEmail = (input) => {
445
+ const claimValue = appleSignInBooleanClaim(input.claims?.is_private_email);
446
+ if (claimValue !== undefined) {
447
+ return claimValue;
448
+ }
449
+ const email = nonEmptyStringFromUnknown(input.email);
450
+ return email === undefined
451
+ ? undefined
452
+ : email.toLowerCase().endsWith(`@${appleSignInPrivateRelayDomain}`);
453
+ };
454
+ const firstString = (...values) => {
455
+ for (const value of values) {
456
+ const stringValue = nonEmptyStringFromUnknown(value);
457
+ if (stringValue !== undefined) {
458
+ return stringValue;
459
+ }
460
+ }
461
+ return undefined;
462
+ };
463
+ const optionalStringArrayFromUnknown = (value) => Array.isArray(value) && value.every((item) => typeof item === "string")
464
+ ? value
465
+ : undefined;
466
+ const numberFromUnknown = (value) => {
467
+ if (typeof value === "number" && Number.isFinite(value)) {
468
+ return value;
469
+ }
470
+ if (typeof value === "string" && value.trim() !== "") {
471
+ const parsed = Number(value);
472
+ return Number.isFinite(parsed) ? parsed : undefined;
473
+ }
474
+ return undefined;
475
+ };
476
+ const isTokenResponseRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
477
+ const scopeValues = (value) => {
478
+ if (typeof value !== "string" || value.trim() === "") {
479
+ return undefined;
480
+ }
481
+ return value.trim().split(/\s+/);
482
+ };
483
+ const audienceValues = (value) => {
484
+ if (typeof value === "string") {
485
+ return [value];
486
+ }
487
+ if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
488
+ return value;
489
+ }
490
+ return [];
491
+ };
492
+ const oidcIdTokenClaims = (claims, input) => {
493
+ if (nonEmptyStringFromUnknown(claims.sub) === undefined) {
494
+ return "missing_subject";
495
+ }
496
+ if (typeof claims.exp !== "number" || !Number.isFinite(claims.exp)) {
497
+ return "missing_expiration";
498
+ }
499
+ if (typeof claims.iat !== "number" || !Number.isFinite(claims.iat)) {
500
+ return "missing_issued_at";
501
+ }
502
+ const audiences = audienceValues(claims.aud);
503
+ const authorizedParty = input.authorizedParty ?? input.provider.clientId;
504
+ if (audiences.length > 1 && claims.azp === undefined) {
505
+ return "missing_authorized_party";
506
+ }
507
+ if (claims.azp !== undefined && claims.azp !== authorizedParty) {
508
+ return "authorized_party_mismatch";
509
+ }
510
+ if (input.nonce !== undefined && claims.nonce !== String(input.nonce)) {
511
+ return "nonce_mismatch";
512
+ }
513
+ return claims;
514
+ };
515
+ const tokenResponseExtra = (value) => {
516
+ const extra = Object.fromEntries(Object.entries(value).filter(([key]) => ![
517
+ "access_token",
518
+ "token_type",
519
+ "expires_in",
520
+ "refresh_token",
521
+ "id_token",
522
+ "scope",
523
+ ].includes(key)));
524
+ return Object.keys(extra).length === 0 ? undefined : extra;
525
+ };
526
+ const cloneVerifiedOAuthIdentity = (identity) => ({
527
+ ...identity,
528
+ ...(identity.metadata === undefined
529
+ ? {}
530
+ : { metadata: metadataValue(identity.metadata) }),
531
+ });
532
+ const oauthLinkConfirmationChallengeType = "oauth-link-confirmation";
533
+ const oauthLinkConfirmationMetadata = (input) => ({
534
+ userId: input.userId,
535
+ providerId: input.identity.providerId,
536
+ providerAccountId: input.identity.providerAccountId,
537
+ ...(input.identity.email === undefined ? {} : { email: input.identity.email }),
538
+ ...(input.identity.emailVerified === undefined
539
+ ? {}
540
+ : { emailVerified: input.identity.emailVerified }),
541
+ ...(input.identity.metadata === undefined
542
+ ? {}
543
+ : { identityMetadata: metadataValue(input.identity.metadata) }),
544
+ reason: input.reason,
545
+ ...(input.metadata === undefined ? {} : { metadata: metadataValue(input.metadata) }),
546
+ });
547
+ const pendingOAuthLinkConfirmationFromChallenge = (challenge, operation) => {
548
+ const metadata = challenge.metadata ?? {};
549
+ const userId = stringFromUnknown(metadata.userId);
550
+ const providerId = stringFromUnknown(metadata.providerId);
551
+ const providerAccountId = stringFromUnknown(metadata.providerAccountId);
552
+ if (userId === undefined ||
553
+ userId !== challenge.subject ||
554
+ providerId === undefined ||
555
+ providerAccountId === undefined) {
556
+ return Effect.fail(oauthLinkConfirmationError(operation, "OAuth link confirmation challenge is missing required metadata"));
557
+ }
558
+ const email = emailFromUnknown(metadata.email);
559
+ const emailVerified = booleanFromUnknown(metadata.emailVerified);
560
+ const identityMetadata = recordFromUnknown(metadata.identityMetadata);
561
+ const appMetadata = recordFromUnknown(metadata.metadata);
562
+ return Effect.succeed({
563
+ challengeId: challenge.id,
564
+ userId: UserId(userId),
565
+ identity: {
566
+ providerId: OAuthProviderId(providerId),
567
+ providerAccountId,
568
+ ...(email === undefined ? {} : { email }),
569
+ ...(emailVerified === undefined ? {} : { emailVerified }),
570
+ ...(identityMetadata === undefined
571
+ ? {}
572
+ : { metadata: metadataValue(identityMetadata) }),
573
+ },
574
+ reason: stringFromUnknown(metadata.reason) ??
575
+ "manual",
576
+ expiresAt: challenge.expiresAt,
577
+ ...(appMetadata === undefined ? {} : { metadata: metadataValue(appMetadata) }),
578
+ });
579
+ };
580
+ const confirmedOAuthLinkConfirmationFromPending = (input) => ({
581
+ challengeId: input.pending.challengeId,
582
+ userId: input.pending.userId,
583
+ identity: cloneVerifiedOAuthIdentity(input.pending.identity),
584
+ reason: input.pending.reason,
585
+ ...(input.existingAccount === undefined
586
+ ? {}
587
+ : { existingAccount: input.existingAccount }),
588
+ ...(input.pending.metadata === undefined
589
+ ? {}
590
+ : { metadata: metadataValue(input.pending.metadata) }),
591
+ });
592
+ const userInfoRecord = (value) => {
593
+ if (value === undefined) {
594
+ return Effect.succeed(undefined);
595
+ }
596
+ const record = recordFromUnknown(value);
597
+ return record === undefined
598
+ ? Effect.fail(oauthProfileNormalizationError("invalid_userinfo", "OIDC userinfo response must be an object"))
599
+ : Effect.succeed(record);
600
+ };
601
+ const makeOAuthProfile = (input) => ({
602
+ providerId: input.providerId,
603
+ providerAccountId: input.providerAccountId,
604
+ ...(input.email === undefined ? {} : { email: input.email }),
605
+ ...(input.emailVerified === undefined
606
+ ? {}
607
+ : { emailVerified: input.emailVerified }),
608
+ ...(input.name === undefined ? {} : { name: input.name }),
609
+ ...(input.username === undefined ? {} : { username: input.username }),
610
+ ...(input.avatarUrl === undefined ? {} : { avatarUrl: input.avatarUrl }),
611
+ ...(input.metadata === undefined ? {} : { metadata: metadataValue(input.metadata) }),
612
+ });
613
+ const oauthJwksPublicJwkFromUnknown = (value) => {
614
+ const record = recordFromUnknown(value);
615
+ const kty = record === undefined ? undefined : nonEmptyStringFromUnknown(record.kty);
616
+ if (record === undefined || kty === undefined) {
617
+ return undefined;
618
+ }
619
+ const jwk = {};
620
+ for (const [name, member] of Object.entries(record)) {
621
+ if (!oauthJwksPrivateMembers.has(name) && member !== undefined) {
622
+ jwk[name] = member;
623
+ }
624
+ }
625
+ return {
626
+ ...jwk,
627
+ kty,
628
+ };
629
+ };
630
+ const oauthJwksAllowsSignatureUse = (jwk) => {
631
+ const use = nonEmptyStringFromUnknown(jwk.use);
632
+ const keyOps = optionalStringArrayFromUnknown(jwk.key_ops);
633
+ return ((use === undefined || use === "sig") &&
634
+ (keyOps === undefined || keyOps.includes("verify")));
635
+ };
636
+ const oauthJwksDefaultAlgorithms = (jwk) => {
637
+ switch (jwk.kty) {
638
+ case "RSA": {
639
+ return ["RS256"];
640
+ }
641
+ case "EC": {
642
+ return ["ES256"];
643
+ }
644
+ case "OKP": {
645
+ return ["EdDSA"];
646
+ }
647
+ default: {
648
+ return [];
649
+ }
650
+ }
651
+ };
652
+ const oauthJwksAlgorithms = (jwk, allowedAlgorithms) => {
653
+ const jwkAlgorithm = nonEmptyStringFromUnknown(jwk.alg);
654
+ const algorithms = jwkAlgorithm === undefined
655
+ ? oauthJwksDefaultAlgorithms(jwk)
656
+ : isPublicJwksAlgorithm(jwkAlgorithm)
657
+ ? [jwkAlgorithm]
658
+ : [];
659
+ if (allowedAlgorithms === undefined) {
660
+ return algorithms;
661
+ }
662
+ return algorithms.filter((algorithm) => allowedAlgorithms.includes(algorithm));
663
+ };
664
+ const oauthJwksRecordsFromDocument = (provider, document, allowedAlgorithms) => document.keys.flatMap((jwk) => {
665
+ const kid = nonEmptyStringFromUnknown(jwk.kid);
666
+ if (kid === undefined || !oauthJwksAllowsSignatureUse(jwk)) {
667
+ return [];
668
+ }
669
+ return oauthJwksAlgorithms(jwk, allowedAlgorithms).map((alg) => ({
670
+ id: JwtKeyId(kid),
671
+ alg,
672
+ status: "active",
673
+ publicJwk: {
674
+ ...jwk,
675
+ kid,
676
+ alg,
677
+ use: nonEmptyStringFromUnknown(jwk.use) ?? "sig",
678
+ },
679
+ metadata: {
680
+ providerId: provider.id,
681
+ },
682
+ }));
683
+ });
684
+ const oauthJwksKeyUsableAt = (key, now) => now === undefined || key.expiresAt === undefined || Number(now) < Number(key.expiresAt);
685
+ const oauthJwksVerificationKeyMatches = (key, input) => key.id === input.kid &&
686
+ key.status !== "disabled" &&
687
+ key.publicJwk !== undefined &&
688
+ (input.alg === undefined || key.alg === input.alg) &&
689
+ oauthJwksKeyUsableAt(key, input.now);
690
+ const oauthJwksNow = (now) => now === undefined ? currentUnixMillis : Effect.succeed(now);
691
+ const formUrlEncode = (value) => new URLSearchParams([["", value]]).toString().slice(1);
692
+ const clientSecretBasic = (provider, secret) => `Basic ${Encoding.encodeBase64(textEncoder.encode(`${formUrlEncode(provider.clientId)}:${formUrlEncode(secret)}`))}`;
693
+ const oauthTokenRequestHeaders = () => ({
694
+ "content-type": "application/x-www-form-urlencoded",
695
+ accept: "application/json",
696
+ });
697
+ const oauthRequestHeadersWithoutAuthorization = (headers) => {
698
+ const value = {};
699
+ for (const [name, headerValue] of Object.entries(headers ?? {})) {
700
+ if (name.toLowerCase() !== "authorization") {
701
+ value[name] = headerValue;
702
+ }
703
+ }
704
+ return value;
705
+ };
706
+ const applyOAuthClientAuthentication = (input) => {
707
+ const clientAuthentication = input.clientAuthentication ?? { method: "none" };
708
+ if (clientAuthentication.method === "client_secret_basic") {
709
+ input.body.delete("client_id");
710
+ input.body.delete("client_secret");
711
+ input.headers.authorization = clientSecretBasic(input.provider, Redacted.value(clientAuthentication.clientSecret));
712
+ }
713
+ else {
714
+ input.body.set("client_id", input.provider.clientId);
715
+ }
716
+ if (clientAuthentication.method === "client_secret_post") {
717
+ input.body.set("client_secret", Redacted.value(clientAuthentication.clientSecret));
718
+ }
719
+ };
720
+ export const makeOAuthTokenRequest = (input) => Effect.try({
721
+ try: () => {
722
+ if (input.provider.tokenEndpoint === undefined) {
723
+ throw new Error("OAuth provider tokenEndpoint is required");
724
+ }
725
+ const body = new URLSearchParams(input.params);
726
+ const headers = oauthTokenRequestHeaders();
727
+ body.set("grant_type", "authorization_code");
728
+ body.set("code", String(input.code));
729
+ body.set("redirect_uri", input.redirectUri ?? input.provider.redirectUri);
730
+ body.set("code_verifier", Redacted.value(input.codeVerifier));
731
+ applyOAuthClientAuthentication({
732
+ provider: input.provider,
733
+ body,
734
+ headers,
735
+ clientAuthentication: input.clientAuthentication,
736
+ });
737
+ return {
738
+ url: input.provider.tokenEndpoint,
739
+ method: "POST",
740
+ headers,
741
+ body: body.toString(),
742
+ };
743
+ },
744
+ catch: (cause) => oauthTokenExchangeError("token-request", "Failed to build OAuth token request", cause),
745
+ });
746
+ export const makeOAuthRefreshTokenRequest = (input) => Effect.try({
747
+ try: () => {
748
+ if (input.provider.tokenEndpoint === undefined) {
749
+ throw new Error("OAuth provider tokenEndpoint is required");
750
+ }
751
+ const body = new URLSearchParams(input.params);
752
+ const headers = oauthTokenRequestHeaders();
753
+ body.set("grant_type", "refresh_token");
754
+ body.set("refresh_token", Redacted.value(input.refreshToken));
755
+ if (input.scopes !== undefined) {
756
+ body.set("scope", input.scopes.join(" "));
757
+ }
758
+ applyOAuthClientAuthentication({
759
+ provider: input.provider,
760
+ body,
761
+ headers,
762
+ clientAuthentication: input.clientAuthentication,
763
+ });
764
+ return {
765
+ url: input.provider.tokenEndpoint,
766
+ method: "POST",
767
+ headers,
768
+ body: body.toString(),
769
+ };
770
+ },
771
+ catch: (cause) => oauthTokenExchangeError("refresh-token-request", "Failed to build OAuth refresh token request", cause),
772
+ });
773
+ export const makeOAuthTokenRevocationRequest = (input) => Effect.try({
774
+ try: () => {
775
+ if (input.provider.revocationEndpoint === undefined) {
776
+ throw new Error("OAuth provider revocationEndpoint is required");
777
+ }
778
+ const body = new URLSearchParams(input.params);
779
+ const headers = oauthTokenRequestHeaders();
780
+ body.set("token", Redacted.value(input.token));
781
+ if (input.tokenTypeHint !== undefined) {
782
+ body.set("token_type_hint", input.tokenTypeHint);
783
+ }
784
+ applyOAuthClientAuthentication({
785
+ provider: input.provider,
786
+ body,
787
+ headers,
788
+ clientAuthentication: input.clientAuthentication,
789
+ });
790
+ return {
791
+ url: input.provider.revocationEndpoint,
792
+ method: "POST",
793
+ headers,
794
+ body: body.toString(),
795
+ };
796
+ },
797
+ catch: (cause) => oauthTokenExchangeError("revocation-request", "Failed to build OAuth token revocation request", cause),
798
+ });
799
+ export const makeOAuthUserInfoRequest = (input) => Effect.try({
800
+ try: () => {
801
+ if (input.provider.userInfoEndpoint === undefined) {
802
+ throw new Error("OAuth provider userInfoEndpoint is required");
803
+ }
804
+ const url = new URL(input.provider.userInfoEndpoint);
805
+ for (const [name, value] of Object.entries(input.params ?? {})) {
806
+ url.searchParams.set(name, value);
807
+ }
808
+ return {
809
+ url: url.toString(),
810
+ method: "GET",
811
+ headers: {
812
+ accept: "application/json",
813
+ ...oauthRequestHeadersWithoutAuthorization(input.headers),
814
+ authorization: `Bearer ${Redacted.value(input.accessToken)}`,
815
+ },
816
+ };
817
+ },
818
+ catch: (cause) => oauthUserInfoError("request", "Failed to build OAuth userinfo request", cause),
819
+ });
820
+ export const decodeOAuthUserInfoResponse = (value) => {
821
+ const record = recordFromUnknown(value);
822
+ return record === undefined
823
+ ? Effect.fail(oauthUserInfoError("decode", "OAuth userinfo response must be an object"))
824
+ : Effect.succeed(metadataValue(record) ?? {});
825
+ };
826
+ export const decodeOAuthTokenResponse = (value) => Effect.gen(function* () {
827
+ if (!isTokenResponseRecord(value)) {
828
+ return yield* oauthTokenExchangeError("decode-token-response", "OAuth token response must be an object");
829
+ }
830
+ const accessToken = stringFromUnknown(value.access_token);
831
+ const tokenType = stringFromUnknown(value.token_type);
832
+ if (accessToken === undefined) {
833
+ return yield* oauthTokenExchangeError("decode-token-response", "OAuth token response is missing access_token");
834
+ }
835
+ if (tokenType === undefined) {
836
+ return yield* oauthTokenExchangeError("decode-token-response", "OAuth token response is missing token_type");
837
+ }
838
+ const refreshToken = stringFromUnknown(value.refresh_token);
839
+ const idToken = stringFromUnknown(value.id_token);
840
+ return {
841
+ accessToken: Redacted.make(OAuthAccessToken(accessToken)),
842
+ tokenType,
843
+ expiresIn: numberFromUnknown(value.expires_in),
844
+ ...(refreshToken === undefined
845
+ ? {}
846
+ : { refreshToken: Redacted.make(OAuthRefreshToken(refreshToken)) }),
847
+ ...(idToken === undefined
848
+ ? {}
849
+ : { idToken: Redacted.make(OAuthIdToken(idToken)) }),
850
+ scopes: scopeValues(value.scope),
851
+ extra: tokenResponseExtra(value),
852
+ };
853
+ });
854
+ export const makeOidcIdTokenVerifier = (dependencies) => OidcIdTokenVerifier.make({
855
+ verify: Effect.fn("auth.oidc_id_token.verify")(function* (input) {
856
+ const { provider } = input;
857
+ const { issuer } = provider;
858
+ if (issuer === undefined) {
859
+ return {
860
+ valid: false,
861
+ reason: "missing_provider_issuer",
862
+ };
863
+ }
864
+ const verified = yield* dependencies.jwtVerifier
865
+ .verify({
866
+ token: Redacted.make(Redacted.value(input.idToken)),
867
+ issuer,
868
+ audience: input.audience ?? provider.clientId,
869
+ now: input.now,
870
+ clockTolerance: input.clockTolerance,
871
+ })
872
+ .pipe(Effect.mapError((cause) => oidcIdTokenVerifierError("verify", "Failed to verify OIDC ID token JWT", cause)));
873
+ if (!verified.valid) {
874
+ return verified;
875
+ }
876
+ const claims = oidcIdTokenClaims(verified.claims, input);
877
+ if (typeof claims === "string") {
878
+ return { valid: false, reason: claims };
879
+ }
880
+ return {
881
+ valid: true,
882
+ header: verified.header,
883
+ claims,
884
+ key: verified.key,
885
+ };
886
+ }),
887
+ });
888
+ export const verifyOidcIdToken = (input) => OidcIdTokenVerifier.use((verifier) => verifier.verify(input));
889
+ export const decodeOAuthJwksDocument = (input) => Effect.gen(function* () {
890
+ const record = recordFromUnknown(input);
891
+ if (record === undefined || !Array.isArray(record.keys)) {
892
+ return yield* oauthJwksJwtError("OAuth JWKS document must contain keys");
893
+ }
894
+ const keys = [];
895
+ for (const key of record.keys) {
896
+ const publicJwk = oauthJwksPublicJwkFromUnknown(key);
897
+ if (publicJwk === undefined) {
898
+ return yield* oauthJwksJwtError("OAuth JWKS key must be a public JWK");
899
+ }
900
+ keys.push(publicJwk);
901
+ }
902
+ return { keys };
903
+ });
904
+ export const makeOAuthJwksJwtKeys = (dependencies) => {
905
+ const cacheTtlMillis = Math.max(0, Duration.toMillis(dependencies.cacheTtl ?? defaultOAuthJwksCacheTtl));
906
+ const allowedAlgorithms = dependencies.allowedAlgorithms?.filter(isPublicJwksAlgorithm);
907
+ let cache;
908
+ const loadRecords = (now) => Effect.gen(function* () {
909
+ if (cache !== undefined && Number(now) < Number(cache.expiresAt)) {
910
+ return cache.records;
911
+ }
912
+ const url = nonEmptyStringFromUnknown(dependencies.provider.jwksUri);
913
+ if (url === undefined) {
914
+ return yield* oauthJwksJwtError("OAuth provider JWKS URI is required");
915
+ }
916
+ const rawDocument = yield* dependencies.fetch({
917
+ provider: dependencies.provider,
918
+ url,
919
+ });
920
+ const document = yield* decodeOAuthJwksDocument(rawDocument);
921
+ const records = oauthJwksRecordsFromDocument(dependencies.provider, document, allowedAlgorithms);
922
+ cache = {
923
+ records,
924
+ expiresAt: UnixMillis(Number(now) + cacheTtlMillis),
925
+ };
926
+ return records;
927
+ });
928
+ return JwtKeys.make({
929
+ selectSigningKey: () => Effect.succeed(Option.none()),
930
+ findVerificationKey: Effect.fn("auth.oauth_jwks.find_verification_key")(function* (input) {
931
+ const now = yield* oauthJwksNow(input.now);
932
+ const records = yield* loadRecords(now);
933
+ const key = records.find((record) => oauthJwksVerificationKeyMatches(record, { ...input, now }));
934
+ return key === undefined ? Option.none() : Option.some(key);
935
+ }),
936
+ listPublicJwks: Effect.fn("auth.oauth_jwks.list_public_jwks")(function* (input) {
937
+ const now = yield* oauthJwksNow(input?.now);
938
+ const records = yield* loadRecords(now);
939
+ return records.flatMap((record) => record.publicJwk === undefined || !oauthJwksKeyUsableAt(record, now)
940
+ ? []
941
+ : [record.publicJwk]);
942
+ }),
943
+ });
944
+ };
945
+ export const normalizeOidcProfile = (input) => Effect.gen(function* () {
946
+ const { claims, provider } = input;
947
+ if (claims === undefined) {
948
+ return yield* oauthProfileNormalizationError("missing_claims", "OIDC ID token claims are required to normalize an OAuth profile");
949
+ }
950
+ const subject = nonEmptyStringFromUnknown(claims.sub);
951
+ if (subject === undefined) {
952
+ return yield* oauthProfileNormalizationError("missing_subject", "OIDC profile is missing subject");
953
+ }
954
+ const userInfo = yield* userInfoRecord(input.userInfo);
955
+ const userInfoSubject = userInfo === undefined ? undefined : nonEmptyStringFromUnknown(userInfo.sub);
956
+ if (userInfoSubject !== undefined && userInfoSubject !== subject) {
957
+ return yield* oauthProfileNormalizationError("subject_mismatch", "OIDC userinfo subject does not match ID token subject");
958
+ }
959
+ const email = emailFromUnknown(userInfo?.email) ?? emailFromUnknown(claims.email);
960
+ const emailVerified = booleanFromUnknown(userInfo?.email_verified) ??
961
+ booleanFromUnknown(claims.email_verified);
962
+ const metadata = optionalRecord({
963
+ ...(input.metadata === undefined ? {} : input.metadata),
964
+ });
965
+ return makeOAuthProfile({
966
+ providerId: provider.id,
967
+ providerAccountId: subject,
968
+ email,
969
+ emailVerified,
970
+ name: firstString(userInfo?.name, claims.name),
971
+ username: firstString(userInfo?.preferred_username, claims.preferred_username),
972
+ avatarUrl: firstString(userInfo?.picture, claims.picture),
973
+ metadata,
974
+ });
975
+ });
976
+ export const makeOidcOAuthProfileNormalizer = () => OAuthProfileNormalizer.make({
977
+ normalize: Effect.fn("auth.oauth_profile.normalize_oidc")(normalizeOidcProfile),
978
+ });
979
+ export const normalizeOAuthProfile = (input) => OAuthProfileNormalizer.use((normalizer) => normalizer.normalize(input));
980
+ export const verifiedOAuthIdentityFromProfile = (profile) => {
981
+ const metadata = optionalRecord({
982
+ ...(profile.name === undefined ? {} : { name: profile.name }),
983
+ ...(profile.username === undefined ? {} : { username: profile.username }),
984
+ ...(profile.avatarUrl === undefined ? {} : { avatarUrl: profile.avatarUrl }),
985
+ ...(profile.metadata === undefined ? {} : profile.metadata),
986
+ });
987
+ return {
988
+ providerId: profile.providerId,
989
+ providerAccountId: profile.providerAccountId,
990
+ ...(profile.email === undefined ? {} : { email: profile.email }),
991
+ ...(profile.emailVerified === undefined
992
+ ? {}
993
+ : { emailVerified: profile.emailVerified }),
994
+ ...(metadata === undefined ? {} : { metadata: metadataValue(metadata) }),
995
+ };
996
+ };
997
+ export const makeOAuthTokenExchange = (dependencies) => OAuthTokenExchange.make({
998
+ exchangeAuthorizationCode: Effect.fn("auth.oauth_token.exchange_code")(function* (input) {
999
+ const request = yield* makeOAuthTokenRequest(input);
1000
+ const response = yield* dependencies.request(request);
1001
+ return yield* decodeOAuthTokenResponse(response).pipe(Effect.mapError((cause) => oauthTokenExchangeError("exchange-code", "Failed to decode OAuth token exchange response", cause)));
1002
+ }),
1003
+ });
1004
+ const oauthProviderTokenString = (token) => Redacted.make(String(Redacted.value(token)));
1005
+ const oauthProviderTokenExpiresAt = (input) => input.expiresIn === undefined
1006
+ ? undefined
1007
+ : UnixMillis(Number(input.now) + input.expiresIn * 1000);
1008
+ const storeOAuthProviderTokenResponse = (dependencies, input, operation) => Effect.gen(function* () {
1009
+ const now = input.now ?? (yield* currentUnixMillis);
1010
+ const accessTokenCiphertext = yield* dependencies
1011
+ .encryptToken(oauthProviderTokenString(input.tokens.accessToken))
1012
+ .pipe(Effect.mapError((cause) => oauthProviderTokenLifecycleError(operation, "Failed to encrypt OAuth provider access token", cause)));
1013
+ const refreshTokenCiphertext = input.tokens.refreshToken === undefined
1014
+ ? input.fallbackRefreshTokenCiphertext
1015
+ : yield* dependencies
1016
+ .encryptToken(oauthProviderTokenString(input.tokens.refreshToken))
1017
+ .pipe(Effect.mapError((cause) => oauthProviderTokenLifecycleError(operation, "Failed to encrypt OAuth provider refresh token", cause)));
1018
+ const idTokenCiphertext = input.tokens.idToken === undefined
1019
+ ? undefined
1020
+ : yield* dependencies
1021
+ .encryptToken(oauthProviderTokenString(input.tokens.idToken))
1022
+ .pipe(Effect.mapError((cause) => oauthProviderTokenLifecycleError(operation, "Failed to encrypt OAuth provider ID token", cause)));
1023
+ const expiresAt = oauthProviderTokenExpiresAt({
1024
+ now,
1025
+ expiresIn: input.tokens.expiresIn,
1026
+ });
1027
+ yield* dependencies.tokenVault
1028
+ .upsert({
1029
+ accountId: input.accountId,
1030
+ userId: input.userId,
1031
+ providerId: input.providerId,
1032
+ providerAccountId: input.providerAccountId,
1033
+ accessTokenCiphertext,
1034
+ tokenType: input.tokens.tokenType,
1035
+ updatedAt: now,
1036
+ ...(refreshTokenCiphertext === undefined ? {} : { refreshTokenCiphertext }),
1037
+ ...(idTokenCiphertext === undefined ? {} : { idTokenCiphertext }),
1038
+ ...(input.tokens.scopes === undefined ? {} : { scopes: input.tokens.scopes }),
1039
+ ...(expiresAt === undefined ? {} : { expiresAt }),
1040
+ })
1041
+ .pipe(Effect.mapError((cause) => oauthProviderTokenLifecycleError(operation, "Failed to store OAuth provider token ciphertext", cause)));
1042
+ return {
1043
+ accountId: input.accountId,
1044
+ ...(expiresAt === undefined ? {} : { expiresAt }),
1045
+ hasRefreshToken: refreshTokenCiphertext !== undefined,
1046
+ scopes: [...(input.tokens.scopes ?? [])],
1047
+ };
1048
+ });
1049
+ const revokeOAuthProviderTokenCiphertext = (dependencies, input, tokenCiphertext, tokenTypeHint) => Effect.gen(function* () {
1050
+ const token = yield* dependencies.decryptToken(tokenCiphertext).pipe(Effect.mapError((cause) => oauthProviderTokenLifecycleError("revoke", `Failed to decrypt OAuth provider ${tokenTypeHint}`, cause)));
1051
+ const request = yield* makeOAuthTokenRevocationRequest({
1052
+ provider: input.provider,
1053
+ token,
1054
+ tokenTypeHint,
1055
+ ...(input.clientAuthentication === undefined
1056
+ ? {}
1057
+ : { clientAuthentication: input.clientAuthentication }),
1058
+ ...(input.params === undefined ? {} : { params: input.params }),
1059
+ }).pipe(Effect.mapError((cause) => oauthProviderTokenLifecycleError("revoke", "Failed to build OAuth provider token revocation request", cause)));
1060
+ yield* dependencies.request(request).pipe(Effect.mapError((cause) => oauthProviderTokenLifecycleError("revoke", "Failed to execute OAuth provider token revocation request", cause)));
1061
+ });
1062
+ export const makeOAuthProviderTokenLifecycle = (dependencies) => OAuthProviderTokenLifecycle.make({
1063
+ store: Effect.fn("auth.oauth_provider_token_lifecycle.store")(function* (input) {
1064
+ return yield* storeOAuthProviderTokenResponse(dependencies, input, "store");
1065
+ }),
1066
+ refresh: Effect.fn("auth.oauth_provider_token_lifecycle.refresh")(function* (input) {
1067
+ const refreshTokenCiphertext = yield* dependencies.tokenVault
1068
+ .refreshTokenForAccount({ accountId: input.accountId })
1069
+ .pipe(Effect.mapError((cause) => oauthProviderTokenLifecycleError("refresh", "Failed to load OAuth provider refresh token ciphertext", cause)));
1070
+ if (Option.isNone(refreshTokenCiphertext)) {
1071
+ return {
1072
+ status: "missing-refresh-token",
1073
+ accountId: input.accountId,
1074
+ };
1075
+ }
1076
+ const refreshToken = yield* dependencies
1077
+ .decryptToken(refreshTokenCiphertext.value)
1078
+ .pipe(Effect.mapError((cause) => oauthProviderTokenLifecycleError("refresh", "Failed to decrypt OAuth provider refresh token", cause)));
1079
+ const request = yield* makeOAuthRefreshTokenRequest({
1080
+ provider: input.provider,
1081
+ refreshToken,
1082
+ ...(input.scopes === undefined ? {} : { scopes: input.scopes }),
1083
+ ...(input.clientAuthentication === undefined
1084
+ ? {}
1085
+ : { clientAuthentication: input.clientAuthentication }),
1086
+ ...(input.params === undefined ? {} : { params: input.params }),
1087
+ }).pipe(Effect.mapError((cause) => oauthProviderTokenLifecycleError("refresh", "Failed to build OAuth provider token refresh request", cause)));
1088
+ const rawResponse = yield* dependencies.request(request).pipe(Effect.mapError((cause) => oauthProviderTokenLifecycleError("refresh", "Failed to execute OAuth provider token refresh request", cause)));
1089
+ const tokens = yield* decodeOAuthTokenResponse(rawResponse).pipe(Effect.mapError((cause) => oauthProviderTokenLifecycleError("refresh", "Failed to decode OAuth provider token refresh response", cause)));
1090
+ const stored = yield* storeOAuthProviderTokenResponse(dependencies, {
1091
+ accountId: input.accountId,
1092
+ userId: input.userId,
1093
+ providerId: input.providerId,
1094
+ providerAccountId: input.providerAccountId,
1095
+ tokens,
1096
+ fallbackRefreshTokenCiphertext: refreshTokenCiphertext.value,
1097
+ now: input.now,
1098
+ }, "refresh");
1099
+ return {
1100
+ status: "refreshed",
1101
+ ...stored,
1102
+ };
1103
+ }),
1104
+ revoke: Effect.fn("auth.oauth_provider_token_lifecycle.revoke")(function* (input) {
1105
+ const tokens = yield* dependencies.tokenVault
1106
+ .tokensForAccount({ accountId: input.accountId })
1107
+ .pipe(Effect.mapError((cause) => oauthProviderTokenLifecycleError("revoke", "Failed to load OAuth provider token ciphertext", cause)));
1108
+ if (Option.isNone(tokens)) {
1109
+ return {
1110
+ status: "missing-tokens",
1111
+ accountId: input.accountId,
1112
+ };
1113
+ }
1114
+ yield* revokeOAuthProviderTokenCiphertext(dependencies, input, tokens.value.accessTokenCiphertext, "access_token");
1115
+ if (tokens.value.refreshTokenCiphertext !== undefined) {
1116
+ yield* revokeOAuthProviderTokenCiphertext(dependencies, input, tokens.value.refreshTokenCiphertext, "refresh_token");
1117
+ }
1118
+ const revokedAt = input.now ?? (yield* currentUnixMillis);
1119
+ yield* dependencies.tokenVault
1120
+ .markRevoked({
1121
+ accountId: input.accountId,
1122
+ revokedAt,
1123
+ reason: input.reason,
1124
+ })
1125
+ .pipe(Effect.mapError((cause) => oauthProviderTokenLifecycleError("revoke", "Failed to mark OAuth provider tokens revoked", cause)));
1126
+ return {
1127
+ status: "revoked",
1128
+ accountId: input.accountId,
1129
+ revokedAt,
1130
+ };
1131
+ }),
1132
+ });
1133
+ export const OAuthProviderTokenLifecycleLive = (options) => Layer.effect(OAuthProviderTokenLifecycle)(Effect.gen(function* () {
1134
+ const tokenVault = yield* OAuthProviderTokenVault;
1135
+ return makeOAuthProviderTokenLifecycle({
1136
+ tokenVault,
1137
+ ...options,
1138
+ });
1139
+ }));
1140
+ export const storeOAuthProviderTokens = (input) => OAuthProviderTokenLifecycle.use((lifecycle) => lifecycle.store(input));
1141
+ export const refreshOAuthProviderTokens = (input) => OAuthProviderTokenLifecycle.use((lifecycle) => lifecycle.refresh(input));
1142
+ export const revokeOAuthProviderTokens = (input) => OAuthProviderTokenLifecycle.use((lifecycle) => lifecycle.revoke(input));
1143
+ export const completeOAuthCallback = (input) => Effect.gen(function* () {
1144
+ const oauthState = yield* OAuthState;
1145
+ const tokenExchange = yield* OAuthTokenExchange;
1146
+ const verifiedState = yield* oauthState.verify({ state: input.state });
1147
+ if (verifiedState.providerId !== input.provider.id) {
1148
+ return yield* oauthStateError("verify", "OAuth callback provider does not match verified state");
1149
+ }
1150
+ const tokens = yield* tokenExchange.exchangeAuthorizationCode({
1151
+ provider: input.provider,
1152
+ code: input.code,
1153
+ codeVerifier: input.codeVerifier,
1154
+ redirectUri: input.redirectUri ?? verifiedState.redirectUri ?? input.provider.redirectUri,
1155
+ clientAuthentication: input.clientAuthentication,
1156
+ params: input.tokenParams,
1157
+ });
1158
+ return { state: verifiedState, tokens };
1159
+ });
1160
+ export const makeOAuthAccountLinkingPolicy = (dependencies) => OAuthAccountLinking.make({
1161
+ resolve: Effect.fn("auth.oauth_account_linking.resolve")(function* (input) {
1162
+ const identity = cloneVerifiedOAuthIdentity(input.identity);
1163
+ const linked = yield* dependencies.accounts
1164
+ .findByProviderAccount({
1165
+ providerId: identity.providerId,
1166
+ providerAccountId: identity.providerAccountId,
1167
+ })
1168
+ .pipe(Effect.mapError((cause) => oauthAccountLinkingError("resolve", "Failed to look up OAuth provider account", cause)));
1169
+ if (Option.isSome(linked)) {
1170
+ const account = linked.value;
1171
+ if (input.currentUserId !== undefined &&
1172
+ account.userId !== input.currentUserId) {
1173
+ return {
1174
+ type: "deny",
1175
+ reason: "provider-account-linked-to-different-user",
1176
+ identity,
1177
+ account,
1178
+ currentUserId: input.currentUserId,
1179
+ };
1180
+ }
1181
+ return {
1182
+ type: "sign-in",
1183
+ reason: "linked-account",
1184
+ userId: account.userId,
1185
+ account,
1186
+ };
1187
+ }
1188
+ if (input.currentUserId !== undefined) {
1189
+ return {
1190
+ type: "link-existing-user",
1191
+ reason: "current-user",
1192
+ userId: input.currentUserId,
1193
+ identity,
1194
+ };
1195
+ }
1196
+ if (input.matchedUserId !== undefined) {
1197
+ if (dependencies.autoLinkVerifiedEmail === true &&
1198
+ identity.emailVerified === true) {
1199
+ return {
1200
+ type: "link-existing-user",
1201
+ reason: "verified-email",
1202
+ userId: input.matchedUserId,
1203
+ identity,
1204
+ };
1205
+ }
1206
+ return {
1207
+ type: "require-explicit-linking",
1208
+ reason: identity.emailVerified === true ? "matched-email" : "unverified-email",
1209
+ identity,
1210
+ matchedUserId: input.matchedUserId,
1211
+ };
1212
+ }
1213
+ return {
1214
+ type: "require-explicit-linking",
1215
+ reason: "no-linked-account",
1216
+ identity,
1217
+ };
1218
+ }),
1219
+ });
1220
+ export const resolveOAuthAccountLinking = (input) => OAuthAccountLinking.use((linking) => linking.resolve(input));
1221
+ export const makeOAuthAccountUnlinkingPolicy = (dependencies) => OAuthAccountUnlinking.make({
1222
+ resolve: Effect.fn("auth.oauth_account_unlinking.resolve")(function* (input) {
1223
+ const account = yield* dependencies.accounts
1224
+ .findByProviderAccount({
1225
+ providerId: input.providerId,
1226
+ providerAccountId: input.providerAccountId,
1227
+ })
1228
+ .pipe(Effect.mapError((cause) => oauthAccountUnlinkingError("resolve", "Failed to look up OAuth provider account", cause)));
1229
+ if (Option.isNone(account)) {
1230
+ return {
1231
+ type: "deny-not-found",
1232
+ reason: "provider-account-not-found",
1233
+ userId: input.userId,
1234
+ providerId: input.providerId,
1235
+ providerAccountId: input.providerAccountId,
1236
+ };
1237
+ }
1238
+ const linkedAccount = account.value;
1239
+ if (linkedAccount.userId !== input.userId) {
1240
+ return {
1241
+ type: "deny-not-owner",
1242
+ reason: "provider-account-linked-to-different-user",
1243
+ userId: input.userId,
1244
+ account: linkedAccount,
1245
+ };
1246
+ }
1247
+ const remainingAccounts = yield* dependencies.accounts
1248
+ .listByUser({ userId: input.userId })
1249
+ .pipe(Effect.map((accounts) => accounts.filter((linked) => linked.id !== linkedAccount.id)), Effect.mapError((cause) => oauthAccountUnlinkingError("resolve", "Failed to list OAuth provider accounts for user", cause)));
1250
+ if (dependencies.allowLastAccount !== true && remainingAccounts.length === 0) {
1251
+ return {
1252
+ type: "deny-last-account",
1253
+ reason: "last-linked-account",
1254
+ userId: input.userId,
1255
+ account: linkedAccount,
1256
+ };
1257
+ }
1258
+ if (dependencies.requireStepUp === true && input.stepUpSatisfied !== true) {
1259
+ return {
1260
+ type: "requires-step-up",
1261
+ reason: "step-up-required",
1262
+ userId: input.userId,
1263
+ account: linkedAccount,
1264
+ remainingAccounts,
1265
+ };
1266
+ }
1267
+ return {
1268
+ type: "allow",
1269
+ reason: "owned-account",
1270
+ userId: input.userId,
1271
+ account: linkedAccount,
1272
+ remainingAccounts,
1273
+ };
1274
+ }),
1275
+ });
1276
+ export const resolveOAuthAccountUnlinking = (input) => OAuthAccountUnlinking.use((unlinking) => unlinking.resolve(input));
1277
+ const findOAuthLinkConfirmationExistingAccount = (dependencies, input) => dependencies.accounts
1278
+ .findByProviderAccount({
1279
+ providerId: input.identity.providerId,
1280
+ providerAccountId: input.identity.providerAccountId,
1281
+ })
1282
+ .pipe(Effect.mapError((cause) => oauthLinkConfirmationError(input.operation, "Failed to look up OAuth provider account", cause)), Effect.flatMap((account) => {
1283
+ if (Option.isNone(account)) {
1284
+ return Effect.succeed(account);
1285
+ }
1286
+ if (account.value.userId !== input.userId) {
1287
+ return Effect.fail(oauthLinkConfirmationError(input.operation, "OAuth provider account is linked to a different user"));
1288
+ }
1289
+ return Effect.succeed(account);
1290
+ }));
1291
+ export const makeOAuthLinkConfirmation = (dependencies) => OAuthLinkConfirmation.make({
1292
+ start: Effect.fn("auth.oauth_link_confirmation.start")(function* (input) {
1293
+ const identity = cloneVerifiedOAuthIdentity(input.identity);
1294
+ const reason = input.reason ?? "manual";
1295
+ const existing = yield* findOAuthLinkConfirmationExistingAccount(dependencies, {
1296
+ operation: "start",
1297
+ userId: input.userId,
1298
+ identity,
1299
+ });
1300
+ if (Option.isSome(existing)) {
1301
+ return yield* oauthLinkConfirmationError("start", "OAuth provider account is already linked to this user");
1302
+ }
1303
+ const secret = input.secret ??
1304
+ (yield* dependencies.crypto
1305
+ .randomToken(yield* validateOAuthLinkConfirmationSecretBytes(dependencies.secretBytes ?? defaultOAuthLinkConfirmationSecretBytes))
1306
+ .pipe(Effect.map(Redacted.make), Effect.mapError((cause) => oauthLinkConfirmationError("start", "Failed to generate OAuth link confirmation secret", cause))));
1307
+ const issued = yield* dependencies.challenge
1308
+ .issue({
1309
+ type: oauthLinkConfirmationChallengeType,
1310
+ subject: input.userId,
1311
+ ttl: input.ttl ?? defaultOAuthLinkConfirmationTtl,
1312
+ secret,
1313
+ metadata: oauthLinkConfirmationMetadata({
1314
+ userId: input.userId,
1315
+ identity,
1316
+ reason,
1317
+ metadata: input.metadata,
1318
+ }),
1319
+ })
1320
+ .pipe(Effect.mapError((cause) => oauthLinkConfirmationError("start", "Failed to issue OAuth link confirmation challenge", cause)));
1321
+ return {
1322
+ challengeId: issued.id,
1323
+ userId: input.userId,
1324
+ identity,
1325
+ reason,
1326
+ secret,
1327
+ expiresAt: issued.expiresAt,
1328
+ ...(input.metadata === undefined
1329
+ ? {}
1330
+ : { metadata: metadataValue(input.metadata) }),
1331
+ };
1332
+ }),
1333
+ inspect: Effect.fn("auth.oauth_link_confirmation.inspect")(function* (input) {
1334
+ const inspected = yield* dependencies.challenge
1335
+ .inspect({
1336
+ challengeId: input.challengeId,
1337
+ type: oauthLinkConfirmationChallengeType,
1338
+ secret: input.secret,
1339
+ })
1340
+ .pipe(Effect.mapError((cause) => oauthLinkConfirmationError("inspect", "Invalid OAuth link confirmation challenge", cause)));
1341
+ const pending = yield* pendingOAuthLinkConfirmationFromChallenge(inspected, "inspect");
1342
+ yield* findOAuthLinkConfirmationExistingAccount(dependencies, {
1343
+ operation: "inspect",
1344
+ userId: pending.userId,
1345
+ identity: pending.identity,
1346
+ });
1347
+ return pending;
1348
+ }),
1349
+ confirm: Effect.fn("auth.oauth_link_confirmation.confirm")(function* (input) {
1350
+ const inspected = yield* dependencies.challenge
1351
+ .inspect({
1352
+ challengeId: input.challengeId,
1353
+ type: oauthLinkConfirmationChallengeType,
1354
+ secret: input.secret,
1355
+ })
1356
+ .pipe(Effect.mapError((cause) => oauthLinkConfirmationError("confirm", "Invalid OAuth link confirmation challenge", cause)));
1357
+ const pending = yield* pendingOAuthLinkConfirmationFromChallenge(inspected, "confirm");
1358
+ if (input.currentUserId !== undefined && input.currentUserId !== pending.userId) {
1359
+ return yield* oauthLinkConfirmationError("confirm", "OAuth link confirmation does not match current user");
1360
+ }
1361
+ if (input.expectedProviderId !== undefined &&
1362
+ input.expectedProviderId !== pending.identity.providerId) {
1363
+ return yield* oauthLinkConfirmationError("confirm", "OAuth link confirmation provider mismatch");
1364
+ }
1365
+ if (input.expectedProviderAccountId !== undefined &&
1366
+ input.expectedProviderAccountId !== pending.identity.providerAccountId) {
1367
+ return yield* oauthLinkConfirmationError("confirm", "OAuth link confirmation provider account mismatch");
1368
+ }
1369
+ const existing = yield* findOAuthLinkConfirmationExistingAccount(dependencies, {
1370
+ operation: "confirm",
1371
+ userId: pending.userId,
1372
+ identity: pending.identity,
1373
+ });
1374
+ yield* dependencies.challenge
1375
+ .verify({
1376
+ challengeId: input.challengeId,
1377
+ type: oauthLinkConfirmationChallengeType,
1378
+ secret: input.secret,
1379
+ })
1380
+ .pipe(Effect.mapError((cause) => oauthLinkConfirmationError("confirm", "Failed to consume OAuth link confirmation challenge", cause)));
1381
+ return confirmedOAuthLinkConfirmationFromPending({
1382
+ pending,
1383
+ ...(Option.isSome(existing) ? { existingAccount: existing.value } : {}),
1384
+ });
1385
+ }),
1386
+ });
1387
+ export const startOAuthLinkConfirmation = (input) => OAuthLinkConfirmation.use((confirmation) => confirmation.start(input));
1388
+ export const inspectOAuthLinkConfirmation = (input) => OAuthLinkConfirmation.use((confirmation) => confirmation.inspect(input));
1389
+ export const confirmOAuthLinkConfirmation = (input) => OAuthLinkConfirmation.use((confirmation) => confirmation.confirm(input));
1390
+ const makeOAuthPkceWithCrypto = (dependencies, input) => Effect.gen(function* () {
1391
+ const verifier = input?.codeVerifier === undefined
1392
+ ? yield* dependencies.crypto
1393
+ .randomToken(yield* validateByteLength(input?.verifierBytes ??
1394
+ dependencies.codeVerifierBytes ??
1395
+ defaultOAuthCodeVerifierBytes, "pkce", "OAuth code verifier"))
1396
+ .pipe(Effect.map(OAuthCodeVerifier), Effect.mapError((cause) => oauthStateError("pkce", "Failed to generate OAuth code verifier", cause)))
1397
+ : OAuthCodeVerifier(Redacted.value(input.codeVerifier));
1398
+ const codeChallenge = yield* dependencies.crypto
1399
+ .digestSha256({ data: verifier })
1400
+ .pipe(Effect.map(OAuthCodeChallenge), Effect.mapError((cause) => oauthStateError("pkce", "Failed to calculate OAuth PKCE challenge", cause)));
1401
+ return {
1402
+ codeVerifier: Redacted.make(verifier),
1403
+ codeChallenge,
1404
+ codeChallengeMethod: "S256",
1405
+ };
1406
+ });
1407
+ export const makeOAuthPkce = (input) => Effect.gen(function* () {
1408
+ const crypto = yield* Crypto;
1409
+ return yield* makeOAuthPkceWithCrypto({ crypto }, input);
1410
+ });
1411
+ const appendParams = (url, params) => {
1412
+ if (params === undefined) {
1413
+ return;
1414
+ }
1415
+ for (const [key, value] of Object.entries(params)) {
1416
+ if (value !== undefined) {
1417
+ url.searchParams.set(key, value);
1418
+ }
1419
+ }
1420
+ };
1421
+ const makeAuthorizationUrl = (input) => Effect.try({
1422
+ try: () => {
1423
+ const url = new URL(input.provider.authorizationEndpoint);
1424
+ appendParams(url, input.provider.authorizationParams);
1425
+ appendParams(url, input.params);
1426
+ appendParams(url, {
1427
+ response_type: "code",
1428
+ client_id: input.provider.clientId,
1429
+ redirect_uri: input.redirectUri,
1430
+ scope: input.scopes.length === 0 ? undefined : input.scopes.join(" "),
1431
+ state: input.state,
1432
+ code_challenge: input.pkce.codeChallenge,
1433
+ code_challenge_method: input.pkce.codeChallengeMethod,
1434
+ nonce: input.nonce,
1435
+ });
1436
+ return url.toString();
1437
+ },
1438
+ catch: (cause) => oauthStateError("authorization-url", "Failed to build OAuth authorization URL", cause),
1439
+ });
1440
+ export const makeOAuthState = (dependencies) => OAuthState.make({
1441
+ start: Effect.fn("auth.oauth_state.start")(function* (input) {
1442
+ const stateSecretBytes = yield* validateByteLength(dependencies.stateSecretBytes ?? defaultOAuthStateSecretBytes, "start", "OAuth state secret");
1443
+ const stateSecret = yield* dependencies.crypto.randomToken(stateSecretBytes).pipe(Effect.map(Redacted.make), Effect.mapError((cause) => oauthStateError("start", "Failed to generate OAuth state secret", cause)));
1444
+ const pkce = yield* makeOAuthPkceWithCrypto(dependencies, {
1445
+ codeVerifier: input.codeVerifier,
1446
+ verifierBytes: dependencies.codeVerifierBytes,
1447
+ });
1448
+ const includeNonce = input.includeNonce ?? input.provider.issuer !== undefined;
1449
+ const nonce = input.nonce === undefined && !includeNonce
1450
+ ? undefined
1451
+ : input.nonce === undefined
1452
+ ? yield* dependencies.crypto
1453
+ .randomToken(dependencies.nonceBytes ?? defaultOAuthNonceBytes)
1454
+ .pipe(Effect.map(OAuthNonce), Effect.mapError((cause) => oauthStateError("start", "Failed to generate OAuth nonce", cause)))
1455
+ : OAuthNonce(input.nonce);
1456
+ const redirectUri = input.redirectUri ?? input.provider.redirectUri;
1457
+ const scopes = input.scopes ?? input.provider.scopes ?? [];
1458
+ const issued = yield* dependencies.challenge
1459
+ .issue({
1460
+ type: "oauth-state",
1461
+ subject: input.provider.id,
1462
+ ttl: input.ttl ?? defaultOAuthStateTtl,
1463
+ secret: stateSecret,
1464
+ metadata: {
1465
+ providerId: input.provider.id,
1466
+ redirectUri,
1467
+ scopes,
1468
+ codeChallenge: pkce.codeChallenge,
1469
+ ...(nonce === undefined ? {} : { nonce }),
1470
+ ...(input.metadata === undefined ? {} : { metadata: input.metadata }),
1471
+ },
1472
+ })
1473
+ .pipe(Effect.mapError((cause) => oauthStateError("start", "Failed to issue OAuth state challenge", cause)));
1474
+ const state = encodeOAuthState({ challengeId: issued.id, secret: stateSecret });
1475
+ const authorizationUrl = yield* makeAuthorizationUrl({
1476
+ provider: input.provider,
1477
+ redirectUri,
1478
+ scopes,
1479
+ params: input.authorizationParams,
1480
+ state,
1481
+ pkce,
1482
+ nonce,
1483
+ });
1484
+ return {
1485
+ providerId: input.provider.id,
1486
+ authorizationUrl,
1487
+ state,
1488
+ challengeId: issued.id,
1489
+ expiresAt: issued.expiresAt,
1490
+ codeVerifier: pkce.codeVerifier,
1491
+ codeChallenge: pkce.codeChallenge,
1492
+ codeChallengeMethod: pkce.codeChallengeMethod,
1493
+ ...(nonce === undefined ? {} : { nonce }),
1494
+ redirectUri,
1495
+ scopes,
1496
+ };
1497
+ }),
1498
+ verify: Effect.fn("auth.oauth_state.verify")(function* (input) {
1499
+ const parsed = yield* parseOAuthState(input.state);
1500
+ const verified = yield* dependencies.challenge
1501
+ .verify({
1502
+ challengeId: parsed.challengeId,
1503
+ type: "oauth-state",
1504
+ secret: parsed.secret,
1505
+ })
1506
+ .pipe(Effect.mapError((cause) => oauthStateError("verify", "Failed to verify OAuth state challenge", cause)));
1507
+ const metadata = verified.metadata ?? {};
1508
+ const appMetadata = metadataValue(typeof metadata.metadata === "object" && metadata.metadata !== null
1509
+ ? metadata.metadata
1510
+ : undefined);
1511
+ const codeChallenge = stringFromUnknown(metadata.codeChallenge);
1512
+ const nonce = stringFromUnknown(metadata.nonce);
1513
+ return {
1514
+ challengeId: parsed.challengeId,
1515
+ providerId: OAuthProviderId(verified.subject),
1516
+ redirectUri: stringFromUnknown(metadata.redirectUri),
1517
+ scopes: stringArrayFromUnknown(metadata.scopes),
1518
+ ...(codeChallenge === undefined
1519
+ ? {}
1520
+ : { codeChallenge: OAuthCodeChallenge(codeChallenge) }),
1521
+ ...(nonce === undefined ? {} : { nonce: OAuthNonce(nonce) }),
1522
+ ...(appMetadata === undefined ? {} : { metadata: appMetadata }),
1523
+ };
1524
+ }),
1525
+ });
1526
+ export const OAuthStateLive = Layer.effect(OAuthState)(Effect.gen(function* () {
1527
+ const crypto = yield* Crypto;
1528
+ const challenge = yield* Challenge;
1529
+ return makeOAuthState({ crypto, challenge });
1530
+ }));
1531
+ export const makeOAuthProviders = (providers) => {
1532
+ const providersById = new Map(providers.map((provider) => [provider.id, cloneProviderConfig(provider)]));
1533
+ return OAuthProviders.make({
1534
+ get: (id) => Effect.sync(() => {
1535
+ const provider = providersById.get(id);
1536
+ return provider === undefined
1537
+ ? Option.none()
1538
+ : Option.some(cloneProviderConfig(provider));
1539
+ }),
1540
+ });
1541
+ };
1542
+ export const OAuthProvidersLive = (providers) => Layer.succeed(OAuthProviders)(makeOAuthProviders(providers));
1543
+ export const OidcIdTokenVerifierLive = Layer.effect(OidcIdTokenVerifier)(Effect.gen(function* () {
1544
+ const jwtVerifier = yield* JwtVerifier;
1545
+ return makeOidcIdTokenVerifier({ jwtVerifier });
1546
+ }));
1547
+ export const OAuthOidcProfileNormalizerLive = Layer.succeed(OAuthProfileNormalizer, makeOidcOAuthProfileNormalizer());
1548
+ export const OAuthAccountLinkingLive = Layer.effect(OAuthAccountLinking)(Effect.gen(function* () {
1549
+ const accounts = yield* OAuthAccountStore;
1550
+ return makeOAuthAccountLinkingPolicy({ accounts });
1551
+ }));
1552
+ export const OAuthAccountUnlinkingLive = Layer.effect(OAuthAccountUnlinking)(Effect.gen(function* () {
1553
+ const accounts = yield* OAuthAccountStore;
1554
+ return makeOAuthAccountUnlinkingPolicy({ accounts });
1555
+ }));
1556
+ export const OAuthLinkConfirmationLive = Layer.effect(OAuthLinkConfirmation)(Effect.gen(function* () {
1557
+ const challenge = yield* Challenge;
1558
+ const accounts = yield* OAuthAccountStore;
1559
+ const crypto = yield* Crypto;
1560
+ return makeOAuthLinkConfirmation({ challenge, accounts, crypto });
1561
+ }));
1562
+ const accountKey = (input) => `${input.providerId}:${input.providerAccountId}`;
1563
+ const cloneOAuthAccountRecord = (record) => ({
1564
+ ...record,
1565
+ ...(record.metadata === undefined ? {} : { metadata: metadataValue(record.metadata) }),
1566
+ });
1567
+ const cloneProviderTokenVaultRecord = (record) => ({
1568
+ ...record,
1569
+ ...(record.scopes === undefined ? {} : { scopes: [...record.scopes] }),
1570
+ });
1571
+ const providerTokenVaultTokens = (record) => ({
1572
+ accessTokenCiphertext: record.accessTokenCiphertext,
1573
+ ...(record.refreshTokenCiphertext === undefined
1574
+ ? {}
1575
+ : { refreshTokenCiphertext: record.refreshTokenCiphertext }),
1576
+ ...(record.idTokenCiphertext === undefined
1577
+ ? {}
1578
+ : { idTokenCiphertext: record.idTokenCiphertext }),
1579
+ tokenType: record.tokenType,
1580
+ ...(record.scopes === undefined ? {} : { scopes: [...record.scopes] }),
1581
+ ...(record.expiresAt === undefined ? {} : { expiresAt: record.expiresAt }),
1582
+ });
1583
+ const activeProviderTokenRecord = (record) => record === undefined || record.revokedAt !== undefined ? undefined : record;
1584
+ const includeAccount = (record, includeUnlinked) => includeUnlinked === true || record.unlinkedAt === undefined;
1585
+ export const makeOAuthProviderTokenVaultMemory = () => {
1586
+ const recordsByAccountId = new Map();
1587
+ return OAuthProviderTokenVault.make({
1588
+ upsert: (record) => Effect.sync(() => {
1589
+ recordsByAccountId.set(record.accountId, cloneProviderTokenVaultRecord(record));
1590
+ }),
1591
+ refreshTokenForAccount: (input) => Effect.sync(() => {
1592
+ const record = activeProviderTokenRecord(recordsByAccountId.get(input.accountId));
1593
+ return record?.refreshTokenCiphertext === undefined
1594
+ ? Option.none()
1595
+ : Option.some(record.refreshTokenCiphertext);
1596
+ }),
1597
+ tokensForAccount: (input) => Effect.sync(() => {
1598
+ const record = activeProviderTokenRecord(recordsByAccountId.get(input.accountId));
1599
+ return record === undefined
1600
+ ? Option.none()
1601
+ : Option.some(providerTokenVaultTokens(record));
1602
+ }),
1603
+ markRevoked: (input) => Effect.sync(() => {
1604
+ const record = recordsByAccountId.get(input.accountId);
1605
+ if (record === undefined) {
1606
+ return Option.none();
1607
+ }
1608
+ const revoked = cloneProviderTokenVaultRecord({
1609
+ ...record,
1610
+ revokedAt: input.revokedAt,
1611
+ revocationReason: input.reason,
1612
+ });
1613
+ recordsByAccountId.set(input.accountId, revoked);
1614
+ return Option.some(cloneProviderTokenVaultRecord(revoked));
1615
+ }),
1616
+ });
1617
+ };
1618
+ export const OAuthProviderTokenVaultMemoryLive = Layer.sync(OAuthProviderTokenVault)(makeOAuthProviderTokenVaultMemory);
1619
+ export const makeOAuthAccountStoreMemory = () => {
1620
+ const accountsById = new Map();
1621
+ const idByProviderAccount = new Map();
1622
+ return OAuthAccountStore.make({
1623
+ insert: (row) => Effect.sync(() => {
1624
+ const cloned = cloneOAuthAccountRecord(row);
1625
+ accountsById.set(row.id, cloned);
1626
+ idByProviderAccount.set(accountKey(row), row.id);
1627
+ }),
1628
+ findById: (id) => Effect.sync(() => {
1629
+ const row = accountsById.get(id);
1630
+ return row === undefined ? Option.none() : Option.some(cloneOAuthAccountRecord(row));
1631
+ }),
1632
+ findByProviderAccount: (input) => Effect.sync(() => {
1633
+ const id = idByProviderAccount.get(accountKey(input));
1634
+ const row = id === undefined ? undefined : accountsById.get(id);
1635
+ return row === undefined || !includeAccount(row, input.includeUnlinked)
1636
+ ? Option.none()
1637
+ : Option.some(cloneOAuthAccountRecord(row));
1638
+ }),
1639
+ listByUser: (input) => Effect.sync(() => [...accountsById.values()]
1640
+ .filter((row) => row.userId === input.userId && includeAccount(row, input.includeUnlinked))
1641
+ .map(cloneOAuthAccountRecord)),
1642
+ unlink: (input) => Effect.sync(() => {
1643
+ const id = idByProviderAccount.get(accountKey(input));
1644
+ const row = id === undefined ? undefined : accountsById.get(id);
1645
+ if (row === undefined) {
1646
+ return Option.none();
1647
+ }
1648
+ const updated = {
1649
+ ...row,
1650
+ unlinkedAt: input.unlinkedAt,
1651
+ metadata: input.reason === undefined
1652
+ ? row.metadata
1653
+ : { ...row.metadata, unlinkReason: input.reason },
1654
+ };
1655
+ accountsById.set(row.id, updated);
1656
+ return Option.some(cloneOAuthAccountRecord(updated));
1657
+ }),
1658
+ });
1659
+ };
1660
+ export const OAuthAccountStoreMemoryLive = Layer.sync(OAuthAccountStore)(makeOAuthAccountStoreMemory);
1661
+ //# sourceMappingURL=OAuth.js.map