@atproto/oauth-provider 0.15.16 → 0.16.1

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 (81) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/account/account-manager.d.ts +2 -1
  3. package/dist/account/account-manager.d.ts.map +1 -1
  4. package/dist/account/account-manager.js +45 -4
  5. package/dist/account/account-manager.js.map +1 -1
  6. package/dist/account/account-store.d.ts +10 -4
  7. package/dist/account/account-store.d.ts.map +1 -1
  8. package/dist/account/account-store.js +2 -1
  9. package/dist/account/account-store.js.map +1 -1
  10. package/dist/customization/branding.d.ts +18 -1
  11. package/dist/customization/branding.d.ts.map +1 -1
  12. package/dist/customization/build-customization-css.d.ts.map +1 -1
  13. package/dist/customization/build-customization-css.js +16 -2
  14. package/dist/customization/build-customization-css.js.map +1 -1
  15. package/dist/customization/build-customization-data.d.ts +2 -2
  16. package/dist/customization/build-customization-data.d.ts.map +1 -1
  17. package/dist/customization/build-customization-data.js.map +1 -1
  18. package/dist/customization/colors.d.ts +11 -2
  19. package/dist/customization/colors.d.ts.map +1 -1
  20. package/dist/customization/colors.js +19 -1
  21. package/dist/customization/colors.js.map +1 -1
  22. package/dist/customization/customization.d.ts +26 -1
  23. package/dist/customization/customization.d.ts.map +1 -1
  24. package/dist/errors/invalid-credentials-error.d.ts +24 -0
  25. package/dist/errors/invalid-credentials-error.d.ts.map +1 -0
  26. package/dist/errors/invalid-credentials-error.js +30 -0
  27. package/dist/errors/invalid-credentials-error.js.map +1 -0
  28. package/dist/lib/http/router.d.ts +2 -1
  29. package/dist/lib/http/router.d.ts.map +1 -1
  30. package/dist/lib/http/router.js +1 -1
  31. package/dist/lib/http/router.js.map +1 -1
  32. package/dist/lib/util/color.d.ts +3 -0
  33. package/dist/lib/util/color.d.ts.map +1 -1
  34. package/dist/lib/util/color.js +33 -1
  35. package/dist/lib/util/color.js.map +1 -1
  36. package/dist/oauth-errors.d.ts +1 -0
  37. package/dist/oauth-errors.d.ts.map +1 -1
  38. package/dist/oauth-errors.js +1 -0
  39. package/dist/oauth-errors.js.map +1 -1
  40. package/dist/oauth-hooks.d.ts +40 -1
  41. package/dist/oauth-hooks.d.ts.map +1 -1
  42. package/dist/oauth-hooks.js +3 -1
  43. package/dist/oauth-hooks.js.map +1 -1
  44. package/dist/oauth-provider.d.ts.map +1 -1
  45. package/dist/oauth-provider.js +6 -11
  46. package/dist/oauth-provider.js.map +1 -1
  47. package/dist/request/request-manager.d.ts +7 -0
  48. package/dist/request/request-manager.d.ts.map +1 -1
  49. package/dist/request/request-manager.js +11 -0
  50. package/dist/request/request-manager.js.map +1 -1
  51. package/dist/result/authorization-result-authorize-page.d.ts +2 -1
  52. package/dist/result/authorization-result-authorize-page.d.ts.map +1 -1
  53. package/dist/result/authorization-result-authorize-page.js.map +1 -1
  54. package/dist/router/assets/assets.d.ts +1 -2
  55. package/dist/router/assets/assets.d.ts.map +1 -1
  56. package/dist/router/assets/assets.js +20 -12
  57. package/dist/router/assets/assets.js.map +1 -1
  58. package/dist/router/assets/send-authorization-page.d.ts.map +1 -1
  59. package/dist/router/assets/send-authorization-page.js +1 -0
  60. package/dist/router/assets/send-authorization-page.js.map +1 -1
  61. package/dist/router/create-api-middleware.d.ts.map +1 -1
  62. package/dist/router/create-api-middleware.js +11 -7
  63. package/dist/router/create-api-middleware.js.map +1 -1
  64. package/package.json +10 -11
  65. package/src/account/account-manager.ts +49 -6
  66. package/src/account/account-store.ts +10 -2
  67. package/src/customization/build-customization-css.ts +25 -3
  68. package/src/customization/build-customization-data.ts +2 -2
  69. package/src/customization/colors.ts +20 -1
  70. package/src/errors/invalid-credentials-error.ts +29 -0
  71. package/src/lib/http/router.ts +7 -3
  72. package/src/lib/util/color.ts +37 -1
  73. package/src/oauth-errors.ts +1 -0
  74. package/src/oauth-hooks.ts +42 -0
  75. package/src/oauth-provider.ts +7 -13
  76. package/src/request/request-manager.ts +12 -0
  77. package/src/result/authorization-result-authorize-page.ts +2 -1
  78. package/src/router/assets/assets.ts +22 -17
  79. package/src/router/assets/send-authorization-page.ts +1 -0
  80. package/src/router/create-api-middleware.ts +17 -6
  81. package/tsconfig.build.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @atproto/oauth-provider
2
2
 
3
+ ## 0.16.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#4857](https://github.com/bluesky-social/atproto/pull/4857) [`c531144`](https://github.com/bluesky-social/atproto/commit/c531144d248f3b88b417fe2bf99b3260225a8cbe) Thanks [@DavidBuchanan314](https://github.com/DavidBuchanan314)! - Add new onSignInFailed hook, plumb clientId through to existing sign-in hooks
8
+
9
+ ## 0.16.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#4820](https://github.com/bluesky-social/atproto/pull/4820) [`b3ce11a`](https://github.com/bluesky-social/atproto/commit/b3ce11ae2e965f239db6aec6054f069d557f4d55) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Unify account management and authorization pages into a signle package
14
+
15
+ ### Patch Changes
16
+
17
+ - [#4820](https://github.com/bluesky-social/atproto/pull/4820) [`b3ce11a`](https://github.com/bluesky-social/atproto/commit/b3ce11ae2e965f239db6aec6054f069d557f4d55) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Account management interface improvements
18
+
19
+ - [#4855](https://github.com/bluesky-social/atproto/pull/4855) [`0cfb16b`](https://github.com/bluesky-social/atproto/commit/0cfb16b2bfead81284317f2f893838384070d219) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Disable COEP headers when hCaptcha is enabled
20
+
21
+ - Updated dependencies [[`26d793a`](https://github.com/bluesky-social/atproto/commit/26d793af95a6fb3a50f9b2a97187d8ac4fecf676), [`26d793a`](https://github.com/bluesky-social/atproto/commit/26d793af95a6fb3a50f9b2a97187d8ac4fecf676), [`b3ce11a`](https://github.com/bluesky-social/atproto/commit/b3ce11ae2e965f239db6aec6054f069d557f4d55), [`26d793a`](https://github.com/bluesky-social/atproto/commit/26d793af95a6fb3a50f9b2a97187d8ac4fecf676), [`b3ce11a`](https://github.com/bluesky-social/atproto/commit/b3ce11ae2e965f239db6aec6054f069d557f4d55), [`55d06de`](https://github.com/bluesky-social/atproto/commit/55d06de80a1506908a04ed5c0834986cb5783797), [`26d793a`](https://github.com/bluesky-social/atproto/commit/26d793af95a6fb3a50f9b2a97187d8ac4fecf676)]:
22
+ - @atproto/syntax@0.5.4
23
+ - @atproto/oauth-provider-api@0.4.0
24
+ - @atproto/oauth-provider-ui@0.5.0
25
+ - @atproto/lex-resolver@0.0.22
26
+ - @atproto/lex-document@0.0.20
27
+
3
28
  ## 0.15.16
4
29
 
5
30
  ### Patch Changes
@@ -1,4 +1,5 @@
1
1
  import { OAuthIssuerIdentifier } from '@atproto/oauth-types';
2
+ import { ClientId } from '../client/client-id.js';
2
3
  import { Client } from '../client/client.js';
3
4
  import { DeviceId } from '../device/device-id.js';
4
5
  import { HCaptchaClient, HcaptchaVerifyResult } from '../lib/hcaptcha.js';
@@ -18,7 +19,7 @@ export declare class AccountManager {
18
19
  protected enforceInviteCode(input: SignUpInput, _deviceId: DeviceId, _deviceMetadata: RequestMetadata): Promise<string | undefined>;
19
20
  protected buildSignupData(input: SignUpInput, deviceId: DeviceId, deviceMetadata: RequestMetadata): Promise<SignUpData>;
20
21
  createAccount(deviceId: DeviceId, deviceMetadata: RequestMetadata, input: SignUpInput): Promise<Account>;
21
- authenticateAccount(deviceId: DeviceId, deviceMetadata: RequestMetadata, data: SignInData): Promise<Account>;
22
+ authenticateAccount(deviceId: DeviceId, deviceMetadata: RequestMetadata, data: SignInData, clientId?: ClientId): Promise<Account>;
22
23
  upsertDeviceAccount(deviceId: DeviceId, sub: Sub): Promise<void>;
23
24
  getDeviceAccount(deviceId: DeviceId, sub: Sub): Promise<DeviceAccount>;
24
25
  setAuthorizedClient(account: Account, client: Client, data: AuthorizedClientData): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"account-manager.d.ts","sourceRoot":"","sources":["../../src/account/account-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EAEtB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AAEjD,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAA;AAEzE,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAA;AACpC,OAAO,EACL,OAAO,EACP,YAAY,EACZ,oBAAoB,EACpB,aAAa,EACb,yBAAyB,EACzB,yBAAyB,EACzB,UAAU,EACX,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAKhD,qBAAa,cAAc;IAMvB,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,YAAY;IACtC,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,UAAU;IANtC,SAAS,CAAC,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAA;IAC9C,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAA;gBAGhD,MAAM,EAAE,qBAAqB,EACV,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,UAAU,EACpC,aAAa,EAAE,aAAa;cAQd,oBAAoB,CAClC,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,QAAQ,EAClB,cAAc,EAAE,eAAe,GAC9B,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC;cAsC5B,iBAAiB,CAC/B,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,QAAQ,EACnB,eAAe,EAAE,eAAe,GAC/B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;cAYd,eAAe,CAC7B,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,QAAQ,EAClB,cAAc,EAAE,eAAe,GAC9B,OAAO,CAAC,UAAU,CAAC;IAST,aAAa,CACxB,QAAQ,EAAE,QAAQ,EAClB,cAAc,EAAE,eAAe,EAC/B,KAAK,EAAE,WAAW,GACjB,OAAO,CAAC,OAAO,CAAC;IAuCN,mBAAmB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,cAAc,EAAE,eAAe,EAC/B,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,OAAO,CAAC;IA+BN,mBAAmB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,GACP,OAAO,CAAC,IAAI,CAAC;IAIH,gBAAgB,CAC3B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,GAAG,GACP,OAAO,CAAC,aAAa,CAAC;IAOZ,mBAAmB,CAC9B,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,IAAI,CAAC;IAOH,UAAU,CAAC,GAAG,EAAE,GAAG;;;;IAInB,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG;IAIhD,kBAAkB,CAC7B,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,aAAa,EAAE,CAAC;IASd,kBAAkB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC;IAStD,oBAAoB,CAC/B,QAAQ,EAAE,QAAQ,EAClB,cAAc,EAAE,eAAe,EAC/B,KAAK,EAAE,yBAAyB;IAwBrB,oBAAoB,CAC/B,QAAQ,EAAE,QAAQ,EAClB,cAAc,EAAE,eAAe,EAC/B,KAAK,EAAE,yBAAyB;IAwBrB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAKrE"}
1
+ {"version":3,"file":"account-manager.d.ts","sourceRoot":"","sources":["../../src/account/account-manager.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AccountManager = void 0;
4
4
  const oauth_types_1 = require("@atproto/oauth-types");
5
+ const invalid_credentials_error_js_1 = require("../errors/invalid-credentials-error.js");
5
6
  const invalid_request_error_js_1 = require("../errors/invalid-request-error.js");
6
7
  const hcaptcha_js_1 = require("../lib/hcaptcha.js");
7
8
  const time_js_1 = require("../lib/util/time.js");
@@ -92,21 +93,61 @@ class AccountManager {
92
93
  throw invalid_request_error_js_1.InvalidRequestError.from(err, 'The account was successfully created but something went wrong, try signing-in.');
93
94
  }
94
95
  }
95
- async authenticateAccount(deviceId, deviceMetadata, data) {
96
+ async authenticateAccount(deviceId, deviceMetadata, data, clientId) {
96
97
  try {
97
98
  await this.hooks.onSignInAttempt?.call(null, {
98
99
  data,
99
100
  deviceId,
100
101
  deviceMetadata,
102
+ clientId,
101
103
  });
102
- const account = await (0, time_js_1.constantTime)(TIMING_ATTACK_MITIGATION_DELAY, async () => {
103
- return this.store.authenticateAccount(data);
104
- });
104
+ let account;
105
+ try {
106
+ account = await (0, time_js_1.constantTime)(TIMING_ATTACK_MITIGATION_DELAY, async () => {
107
+ return this.store.authenticateAccount(data);
108
+ });
109
+ }
110
+ catch (err) {
111
+ // Only notify for credential failures (e.g. unknown identifier, wrong
112
+ // password). Server errors and flows that require an additional factor
113
+ // (e.g. SecondAuthenticationFactorRequiredError) are not "failed
114
+ // sign-ins" and do not trigger the hook.
115
+ if (err instanceof invalid_request_error_js_1.InvalidRequestError) {
116
+ // Stores that throw the more specific `InvalidCredentialsError`
117
+ // can attach the matched subject identifier to distinguish
118
+ // "identifier known, password wrong" from "identifier unknown".
119
+ // This information is only exposed to the hook and is never
120
+ // surfaced to the client.
121
+ const isCredentialsError = err instanceof invalid_credentials_error_js_1.InvalidCredentialsError;
122
+ const sub = isCredentialsError ? err.sub ?? null : null;
123
+ // Swallow any error from the hook itself so that it does not mask
124
+ // the underlying authentication failure being reported.
125
+ try {
126
+ await this.hooks.onSignInFailed?.call(null, {
127
+ data,
128
+ error: err,
129
+ sub,
130
+ deviceId,
131
+ deviceMetadata,
132
+ clientId,
133
+ });
134
+ }
135
+ catch {
136
+ // noop
137
+ }
138
+ if (isCredentialsError) {
139
+ // Defensively downgrade to a plain InvalidRequestError
140
+ throw new invalid_request_error_js_1.InvalidRequestError(err.error_description);
141
+ }
142
+ }
143
+ throw err;
144
+ }
105
145
  await this.hooks.onSignedIn?.call(null, {
106
146
  data,
107
147
  account,
108
148
  deviceId,
109
149
  deviceMetadata,
150
+ clientId,
110
151
  });
111
152
  return account;
112
153
  }
@@ -1 +1 @@
1
- {"version":3,"file":"account-manager.js","sourceRoot":"","sources":["../../src/account/account-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n OAuthIssuerIdentifier,\n isOAuthClientIdLoopback,\n} from '@atproto/oauth-types'\nimport { Client } from '../client/client.js'\nimport { DeviceId } from '../device/device-id.js'\nimport { InvalidRequestError } from '../errors/invalid-request-error.js'\nimport { HCaptchaClient, HcaptchaVerifyResult } from '../lib/hcaptcha.js'\nimport { constantTime } from '../lib/util/time.js'\nimport { OAuthHooks, RequestMetadata } from '../oauth-hooks.js'\nimport { Customization } from '../oauth-provider.js'\nimport { Sub } from '../oidc/sub.js'\nimport {\n Account,\n AccountStore,\n AuthorizedClientData,\n DeviceAccount,\n ResetPasswordConfirmInput,\n ResetPasswordRequestInput,\n SignUpData,\n} from './account-store.js'\nimport { SignInData } from './sign-in-data.js'\nimport { SignUpInput } from './sign-up-input.js'\n\nconst TIMING_ATTACK_MITIGATION_DELAY = 400\nconst BRUTE_FORCE_MITIGATION_DELAY = 300\n\nexport class AccountManager {\n protected readonly inviteCodeRequired: boolean\n protected readonly hcaptchaClient?: HCaptchaClient\n\n constructor(\n issuer: OAuthIssuerIdentifier,\n protected readonly store: AccountStore,\n protected readonly hooks: OAuthHooks,\n customization: Customization,\n ) {\n this.inviteCodeRequired = customization.inviteCodeRequired !== false\n this.hcaptchaClient = customization.hcaptcha\n ? new HCaptchaClient(new URL(issuer).hostname, customization.hcaptcha)\n : undefined\n }\n\n protected async processHcaptchaToken(\n input: SignUpInput,\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n ): Promise<HcaptchaVerifyResult | undefined> {\n if (!this.hcaptchaClient) {\n return undefined\n }\n\n if (!input.hcaptchaToken) {\n throw new InvalidRequestError('hCaptcha token is required')\n }\n\n const tokens = this.hcaptchaClient.buildClientTokens(\n deviceMetadata.ipAddress,\n input.handle,\n deviceMetadata.userAgent,\n )\n\n const result = await this.hcaptchaClient\n .verify('signup', input.hcaptchaToken, deviceMetadata.ipAddress, tokens)\n .catch((err) => {\n throw InvalidRequestError.from(err, 'hCaptcha verification failed')\n })\n\n await this.hooks.onHcaptchaResult?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n tokens,\n result,\n })\n\n try {\n this.hcaptchaClient.checkVerifyResult(result, tokens)\n } catch (err) {\n throw InvalidRequestError.from(err, 'hCaptcha verification failed')\n }\n\n return result\n }\n\n protected async enforceInviteCode(\n input: SignUpInput,\n _deviceId: DeviceId,\n _deviceMetadata: RequestMetadata,\n ): Promise<string | undefined> {\n if (!this.inviteCodeRequired) {\n return undefined\n }\n\n if (!input.inviteCode) {\n throw new InvalidRequestError('Invite code is required')\n }\n\n return input.inviteCode\n }\n\n protected async buildSignupData(\n input: SignUpInput,\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n ): Promise<SignUpData> {\n const [hcaptchaResult, inviteCode] = await Promise.all([\n this.processHcaptchaToken(input, deviceId, deviceMetadata),\n this.enforceInviteCode(input, deviceId, deviceMetadata),\n ])\n\n return { ...input, hcaptchaResult, inviteCode }\n }\n\n public async createAccount(\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n input: SignUpInput,\n ): Promise<Account> {\n await this.hooks.onSignUpAttempt?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n })\n\n const data = await this.buildSignupData(input, deviceId, deviceMetadata)\n\n // Mitigation against brute forcing email of users.\n // @TODO Add rate limit to all the OAuth routes.\n const account = await constantTime(\n BRUTE_FORCE_MITIGATION_DELAY,\n async () => {\n return this.store.createAccount(data)\n },\n ).catch((err) => {\n throw InvalidRequestError.from(err, 'Account creation failed')\n })\n\n try {\n await this.hooks.onSignedUp?.call(null, {\n data,\n account,\n deviceId,\n deviceMetadata,\n })\n\n return account\n } catch (err) {\n await this.removeDeviceAccount(deviceId, account.sub)\n\n throw InvalidRequestError.from(\n err,\n 'The account was successfully created but something went wrong, try signing-in.',\n )\n }\n }\n\n public async authenticateAccount(\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n data: SignInData,\n ): Promise<Account> {\n try {\n await this.hooks.onSignInAttempt?.call(null, {\n data,\n deviceId,\n deviceMetadata,\n })\n\n const account = await constantTime(\n TIMING_ATTACK_MITIGATION_DELAY,\n async () => {\n return this.store.authenticateAccount(data)\n },\n )\n\n await this.hooks.onSignedIn?.call(null, {\n data,\n account,\n deviceId,\n deviceMetadata,\n })\n\n return account\n } catch (err) {\n throw InvalidRequestError.from(\n err,\n 'Unable to sign-in due to an unexpected server error',\n )\n }\n }\n\n public async upsertDeviceAccount(\n deviceId: DeviceId,\n sub: Sub,\n ): Promise<void> {\n await this.store.upsertDeviceAccount(deviceId, sub)\n }\n\n public async getDeviceAccount(\n deviceId: DeviceId,\n sub: Sub,\n ): Promise<DeviceAccount> {\n const deviceAccount = await this.store.getDeviceAccount(deviceId, sub)\n if (!deviceAccount) throw new InvalidRequestError(`Account not found`)\n\n return deviceAccount\n }\n\n public async setAuthorizedClient(\n account: Account,\n client: Client,\n data: AuthorizedClientData,\n ): Promise<void> {\n // \"Loopback\" clients are not distinguishable from one another.\n if (isOAuthClientIdLoopback(client.id)) return\n\n await this.store.setAuthorizedClient(account.sub, client.id, data)\n }\n\n public async getAccount(sub: Sub) {\n return this.store.getAccount(sub)\n }\n\n public async removeDeviceAccount(deviceId: DeviceId, sub: Sub) {\n return this.store.removeDeviceAccount(deviceId, sub)\n }\n\n public async listDeviceAccounts(\n deviceId: DeviceId,\n ): Promise<DeviceAccount[]> {\n const deviceAccounts = await this.store.listDeviceAccounts({\n deviceId,\n })\n\n return deviceAccounts // Fool proof\n .filter((deviceAccount) => deviceAccount.deviceId === deviceId)\n }\n\n public async listAccountDevices(sub: Sub): Promise<DeviceAccount[]> {\n const deviceAccounts = await this.store.listDeviceAccounts({\n sub,\n })\n\n return deviceAccounts // Fool proof\n .filter((deviceAccount) => deviceAccount.account.sub === sub)\n }\n\n public async resetPasswordRequest(\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n input: ResetPasswordRequestInput,\n ) {\n await this.hooks.onResetPasswordRequest?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n })\n\n return constantTime(TIMING_ATTACK_MITIGATION_DELAY, async () => {\n const account = await this.store.resetPasswordRequest(input)\n\n if (!account) {\n return // Silently ignore to prevent user enumeration\n }\n\n await this.hooks.onResetPasswordRequested?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n account,\n })\n })\n }\n\n public async resetPasswordConfirm(\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n input: ResetPasswordConfirmInput,\n ) {\n await this.hooks.onResetPasswordConfirm?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n })\n\n return constantTime(TIMING_ATTACK_MITIGATION_DELAY, async () => {\n const account = await this.store.resetPasswordConfirm(input)\n\n if (!account) {\n throw new InvalidRequestError('Invalid token')\n }\n\n await this.hooks.onResetPasswordConfirmed?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n account,\n })\n })\n }\n\n public async verifyHandleAvailability(handle: string): Promise<void> {\n return constantTime(TIMING_ATTACK_MITIGATION_DELAY, async () => {\n return this.store.verifyHandleAvailability(handle)\n })\n }\n}\n"]}
1
+ {"version":3,"file":"account-manager.js","sourceRoot":"","sources":["../../src/account/account-manager.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n OAuthIssuerIdentifier,\n isOAuthClientIdLoopback,\n} from '@atproto/oauth-types'\nimport { ClientId } from '../client/client-id.js'\nimport { Client } from '../client/client.js'\nimport { DeviceId } from '../device/device-id.js'\nimport { InvalidCredentialsError } from '../errors/invalid-credentials-error.js'\nimport { InvalidRequestError } from '../errors/invalid-request-error.js'\nimport { HCaptchaClient, HcaptchaVerifyResult } from '../lib/hcaptcha.js'\nimport { constantTime } from '../lib/util/time.js'\nimport { OAuthHooks, RequestMetadata } from '../oauth-hooks.js'\nimport { Customization } from '../oauth-provider.js'\nimport { Sub } from '../oidc/sub.js'\nimport {\n Account,\n AccountStore,\n AuthorizedClientData,\n DeviceAccount,\n ResetPasswordConfirmInput,\n ResetPasswordRequestInput,\n SignUpData,\n} from './account-store.js'\nimport { SignInData } from './sign-in-data.js'\nimport { SignUpInput } from './sign-up-input.js'\n\nconst TIMING_ATTACK_MITIGATION_DELAY = 400\nconst BRUTE_FORCE_MITIGATION_DELAY = 300\n\nexport class AccountManager {\n protected readonly inviteCodeRequired: boolean\n protected readonly hcaptchaClient?: HCaptchaClient\n\n constructor(\n issuer: OAuthIssuerIdentifier,\n protected readonly store: AccountStore,\n protected readonly hooks: OAuthHooks,\n customization: Customization,\n ) {\n this.inviteCodeRequired = customization.inviteCodeRequired !== false\n this.hcaptchaClient = customization.hcaptcha\n ? new HCaptchaClient(new URL(issuer).hostname, customization.hcaptcha)\n : undefined\n }\n\n protected async processHcaptchaToken(\n input: SignUpInput,\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n ): Promise<HcaptchaVerifyResult | undefined> {\n if (!this.hcaptchaClient) {\n return undefined\n }\n\n if (!input.hcaptchaToken) {\n throw new InvalidRequestError('hCaptcha token is required')\n }\n\n const tokens = this.hcaptchaClient.buildClientTokens(\n deviceMetadata.ipAddress,\n input.handle,\n deviceMetadata.userAgent,\n )\n\n const result = await this.hcaptchaClient\n .verify('signup', input.hcaptchaToken, deviceMetadata.ipAddress, tokens)\n .catch((err) => {\n throw InvalidRequestError.from(err, 'hCaptcha verification failed')\n })\n\n await this.hooks.onHcaptchaResult?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n tokens,\n result,\n })\n\n try {\n this.hcaptchaClient.checkVerifyResult(result, tokens)\n } catch (err) {\n throw InvalidRequestError.from(err, 'hCaptcha verification failed')\n }\n\n return result\n }\n\n protected async enforceInviteCode(\n input: SignUpInput,\n _deviceId: DeviceId,\n _deviceMetadata: RequestMetadata,\n ): Promise<string | undefined> {\n if (!this.inviteCodeRequired) {\n return undefined\n }\n\n if (!input.inviteCode) {\n throw new InvalidRequestError('Invite code is required')\n }\n\n return input.inviteCode\n }\n\n protected async buildSignupData(\n input: SignUpInput,\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n ): Promise<SignUpData> {\n const [hcaptchaResult, inviteCode] = await Promise.all([\n this.processHcaptchaToken(input, deviceId, deviceMetadata),\n this.enforceInviteCode(input, deviceId, deviceMetadata),\n ])\n\n return { ...input, hcaptchaResult, inviteCode }\n }\n\n public async createAccount(\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n input: SignUpInput,\n ): Promise<Account> {\n await this.hooks.onSignUpAttempt?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n })\n\n const data = await this.buildSignupData(input, deviceId, deviceMetadata)\n\n // Mitigation against brute forcing email of users.\n // @TODO Add rate limit to all the OAuth routes.\n const account = await constantTime(\n BRUTE_FORCE_MITIGATION_DELAY,\n async () => {\n return this.store.createAccount(data)\n },\n ).catch((err) => {\n throw InvalidRequestError.from(err, 'Account creation failed')\n })\n\n try {\n await this.hooks.onSignedUp?.call(null, {\n data,\n account,\n deviceId,\n deviceMetadata,\n })\n\n return account\n } catch (err) {\n await this.removeDeviceAccount(deviceId, account.sub)\n\n throw InvalidRequestError.from(\n err,\n 'The account was successfully created but something went wrong, try signing-in.',\n )\n }\n }\n\n public async authenticateAccount(\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n data: SignInData,\n clientId?: ClientId,\n ): Promise<Account> {\n try {\n await this.hooks.onSignInAttempt?.call(null, {\n data,\n deviceId,\n deviceMetadata,\n clientId,\n })\n\n let account: Account\n try {\n account = await constantTime(\n TIMING_ATTACK_MITIGATION_DELAY,\n async () => {\n return this.store.authenticateAccount(data)\n },\n )\n } catch (err) {\n // Only notify for credential failures (e.g. unknown identifier, wrong\n // password). Server errors and flows that require an additional factor\n // (e.g. SecondAuthenticationFactorRequiredError) are not \"failed\n // sign-ins\" and do not trigger the hook.\n if (err instanceof InvalidRequestError) {\n // Stores that throw the more specific `InvalidCredentialsError`\n // can attach the matched subject identifier to distinguish\n // \"identifier known, password wrong\" from \"identifier unknown\".\n // This information is only exposed to the hook and is never\n // surfaced to the client.\n const isCredentialsError = err instanceof InvalidCredentialsError\n const sub = isCredentialsError ? err.sub ?? null : null\n\n // Swallow any error from the hook itself so that it does not mask\n // the underlying authentication failure being reported.\n try {\n await this.hooks.onSignInFailed?.call(null, {\n data,\n error: err,\n sub,\n deviceId,\n deviceMetadata,\n clientId,\n })\n } catch {\n // noop\n }\n\n if (isCredentialsError) {\n // Defensively downgrade to a plain InvalidRequestError\n throw new InvalidRequestError(err.error_description)\n }\n }\n throw err\n }\n\n await this.hooks.onSignedIn?.call(null, {\n data,\n account,\n deviceId,\n deviceMetadata,\n clientId,\n })\n\n return account\n } catch (err) {\n throw InvalidRequestError.from(\n err,\n 'Unable to sign-in due to an unexpected server error',\n )\n }\n }\n\n public async upsertDeviceAccount(\n deviceId: DeviceId,\n sub: Sub,\n ): Promise<void> {\n await this.store.upsertDeviceAccount(deviceId, sub)\n }\n\n public async getDeviceAccount(\n deviceId: DeviceId,\n sub: Sub,\n ): Promise<DeviceAccount> {\n const deviceAccount = await this.store.getDeviceAccount(deviceId, sub)\n if (!deviceAccount) throw new InvalidRequestError(`Account not found`)\n\n return deviceAccount\n }\n\n public async setAuthorizedClient(\n account: Account,\n client: Client,\n data: AuthorizedClientData,\n ): Promise<void> {\n // \"Loopback\" clients are not distinguishable from one another.\n if (isOAuthClientIdLoopback(client.id)) return\n\n await this.store.setAuthorizedClient(account.sub, client.id, data)\n }\n\n public async getAccount(sub: Sub) {\n return this.store.getAccount(sub)\n }\n\n public async removeDeviceAccount(deviceId: DeviceId, sub: Sub) {\n return this.store.removeDeviceAccount(deviceId, sub)\n }\n\n public async listDeviceAccounts(\n deviceId: DeviceId,\n ): Promise<DeviceAccount[]> {\n const deviceAccounts = await this.store.listDeviceAccounts({\n deviceId,\n })\n\n return deviceAccounts // Fool proof\n .filter((deviceAccount) => deviceAccount.deviceId === deviceId)\n }\n\n public async listAccountDevices(sub: Sub): Promise<DeviceAccount[]> {\n const deviceAccounts = await this.store.listDeviceAccounts({\n sub,\n })\n\n return deviceAccounts // Fool proof\n .filter((deviceAccount) => deviceAccount.account.sub === sub)\n }\n\n public async resetPasswordRequest(\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n input: ResetPasswordRequestInput,\n ) {\n await this.hooks.onResetPasswordRequest?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n })\n\n return constantTime(TIMING_ATTACK_MITIGATION_DELAY, async () => {\n const account = await this.store.resetPasswordRequest(input)\n\n if (!account) {\n return // Silently ignore to prevent user enumeration\n }\n\n await this.hooks.onResetPasswordRequested?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n account,\n })\n })\n }\n\n public async resetPasswordConfirm(\n deviceId: DeviceId,\n deviceMetadata: RequestMetadata,\n input: ResetPasswordConfirmInput,\n ) {\n await this.hooks.onResetPasswordConfirm?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n })\n\n return constantTime(TIMING_ATTACK_MITIGATION_DELAY, async () => {\n const account = await this.store.resetPasswordConfirm(input)\n\n if (!account) {\n throw new InvalidRequestError('Invalid token')\n }\n\n await this.hooks.onResetPasswordConfirmed?.call(null, {\n input,\n deviceId,\n deviceMetadata,\n account,\n })\n })\n }\n\n public async verifyHandleAvailability(handle: string): Promise<void> {\n return constantTime(TIMING_ATTACK_MITIGATION_DELAY, async () => {\n return this.store.verifyHandleAvailability(handle)\n })\n }\n}\n"]}
@@ -1,11 +1,11 @@
1
- import { Account, ConfirmResetPasswordInput, InitiatePasswordResetInput } from '@atproto/oauth-provider-api';
1
+ import type { Account, ConfirmResetPasswordInput, InitiatePasswordResetInput } from '@atproto/oauth-provider-api';
2
2
  import { OAuthScope } from '@atproto/oauth-types';
3
3
  import { ClientId } from '../client/client-id.js';
4
4
  import { DeviceId } from '../device/device-id.js';
5
5
  import { DeviceData } from '../device/device-store.js';
6
6
  import { HcaptchaVerifyResult } from '../lib/hcaptcha.js';
7
7
  import { Awaitable } from '../lib/util/type.js';
8
- import { HandleUnavailableError, InvalidRequestError, SecondAuthenticationFactorRequiredError } from '../oauth-errors.js';
8
+ import { HandleUnavailableError, InvalidCredentialsError, InvalidRequestError, SecondAuthenticationFactorRequiredError } from '../oauth-errors.js';
9
9
  import { Sub } from '../oidc/sub.js';
10
10
  import { InviteCode } from '../types/invite-code.js';
11
11
  import { SignUpInput } from './sign-up-input.js';
@@ -15,7 +15,7 @@ export * from '../device/device-id.js';
15
15
  export * from '../oidc/sub.js';
16
16
  export * from '../request/request-id.js';
17
17
  export type { Account, HcaptchaVerifyResult, InviteCode, OAuthScope, SignUpInput, };
18
- export { HandleUnavailableError, InvalidRequestError, SecondAuthenticationFactorRequiredError, };
18
+ export { HandleUnavailableError, InvalidCredentialsError, InvalidRequestError, SecondAuthenticationFactorRequiredError, };
19
19
  export type ResetPasswordRequestInput = InitiatePasswordResetInput;
20
20
  export type ResetPasswordConfirmInput = ConfirmResetPasswordInput;
21
21
  export type CreateAccountData = {
@@ -74,7 +74,13 @@ export interface AccountStore {
74
74
  */
75
75
  createAccount(data: CreateAccountData): Awaitable<Account>;
76
76
  /**
77
- * @throws {InvalidRequestError} - When the credentials are not valid
77
+ * @throws {InvalidCredentialsError} - When the credentials are not valid.
78
+ * Populate {@link InvalidCredentialsError.sub} with the subject identifier
79
+ * when the identifier matched an existing account (e.g. wrong password for
80
+ * a known user); omit it when the identifier was not found. Throwing the
81
+ * generic {@link InvalidRequestError} is also accepted for backward
82
+ * compatibility but prevents the `onSignInFailed` hook from distinguishing
83
+ * the two cases.
78
84
  * @throws {SecondAuthenticationFactorRequiredError} - To indicate that an {@link SecondAuthenticationFactorRequiredError.type} is required in the credentials
79
85
  */
80
86
  authenticateAccount(data: AuthenticateAccountData): Awaitable<Account>;
@@ -1 +1 @@
1
- {"version":3,"file":"account-store.d.ts","sourceRoot":"","sources":["../../src/account/account-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"account-store.d.ts","sourceRoot":"","sources":["../../src/account/account-store.ts"],"names":[],"mappings":"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"}
@@ -14,11 +14,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.isAccountStore = exports.SecondAuthenticationFactorRequiredError = exports.InvalidRequestError = exports.HandleUnavailableError = void 0;
17
+ exports.isAccountStore = exports.SecondAuthenticationFactorRequiredError = exports.InvalidRequestError = exports.InvalidCredentialsError = exports.HandleUnavailableError = void 0;
18
18
  exports.asAccountStore = asAccountStore;
19
19
  const type_js_1 = require("../lib/util/type.js");
20
20
  const oauth_errors_js_1 = require("../oauth-errors.js");
21
21
  Object.defineProperty(exports, "HandleUnavailableError", { enumerable: true, get: function () { return oauth_errors_js_1.HandleUnavailableError; } });
22
+ Object.defineProperty(exports, "InvalidCredentialsError", { enumerable: true, get: function () { return oauth_errors_js_1.InvalidCredentialsError; } });
22
23
  Object.defineProperty(exports, "InvalidRequestError", { enumerable: true, get: function () { return oauth_errors_js_1.InvalidRequestError; } });
23
24
  Object.defineProperty(exports, "SecondAuthenticationFactorRequiredError", { enumerable: true, get: function () { return oauth_errors_js_1.SecondAuthenticationFactorRequiredError; } });
24
25
  // Export all types needed to implement the AccountStore interface
@@ -1 +1 @@
1
- {"version":3,"file":"account-store.js","sourceRoot":"","sources":["../../src/account/account-store.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAyMA,wCAKC;AApMD,iDAAsE;AACtE,wDAI2B;AAsBzB,uGAzBA,wCAAsB,OAyBA;AACtB,oGAzBA,qCAAmB,OAyBA;AACnB,wHAzBA,yDAAuC,OAyBA;AAnBzC,kEAAkE;AAElE,yDAAsC;AACtC,2DAAwC;AACxC,yDAAsC;AACtC,iDAA8B;AAC9B,2DAAwC;AAiK3B,QAAA,cAAc,GAAG,IAAA,+BAAqB,EAAe;IAChE,eAAe;IACf,qBAAqB;IACrB,qBAAqB;IACrB,YAAY;IACZ,qBAAqB;IACrB,kBAAkB;IAClB,qBAAqB;IACrB,oBAAoB;IACpB,sBAAsB;IACtB,sBAAsB;IACtB,0BAA0B;CAC3B,CAAC,CAAA;AAEF,SAAgB,cAAc,CAAI,cAAiB;IACjD,IAAI,CAAC,cAAc,IAAI,CAAC,IAAA,sBAAc,EAAC,cAAc,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;IACxD,CAAC;IACD,OAAO,cAAc,CAAA;AACvB,CAAC","sourcesContent":["import {\n Account,\n ConfirmResetPasswordInput,\n InitiatePasswordResetInput,\n} from '@atproto/oauth-provider-api'\nimport { OAuthScope } from '@atproto/oauth-types'\nimport { ClientId } from '../client/client-id.js'\nimport { DeviceId } from '../device/device-id.js'\nimport { DeviceData } from '../device/device-store.js'\nimport { HcaptchaVerifyResult } from '../lib/hcaptcha.js'\nimport { Awaitable, buildInterfaceChecker } from '../lib/util/type.js'\nimport {\n HandleUnavailableError,\n InvalidRequestError,\n SecondAuthenticationFactorRequiredError,\n} from '../oauth-errors.js'\nimport { Sub } from '../oidc/sub.js'\nimport { InviteCode } from '../types/invite-code.js'\nimport { SignUpInput } from './sign-up-input.js'\n\n// Export all types needed to implement the AccountStore interface\n\nexport * from '../client/client-id.js'\nexport * from '../device/device-data.js'\nexport * from '../device/device-id.js'\nexport * from '../oidc/sub.js'\nexport * from '../request/request-id.js'\n\nexport type {\n Account,\n HcaptchaVerifyResult,\n InviteCode,\n OAuthScope,\n SignUpInput,\n}\n\nexport {\n HandleUnavailableError,\n InvalidRequestError,\n SecondAuthenticationFactorRequiredError,\n}\n\nexport type ResetPasswordRequestInput = InitiatePasswordResetInput\nexport type ResetPasswordConfirmInput = ConfirmResetPasswordInput\n\nexport type CreateAccountData = {\n locale: string\n email: string\n password: string\n handle: string\n inviteCode?: string | undefined\n}\n\nexport type AuthenticateAccountData = {\n locale: string\n password: string\n username: string\n emailOtp?: string | undefined\n}\n\nexport type AuthorizedClientData = { authorizedScopes: readonly string[] }\nexport type AuthorizedClients = Map<ClientId, AuthorizedClientData>\n\nexport type DeviceAccount = {\n deviceId: DeviceId\n\n /**\n * The data associated with the device, created through the\n * {@link DeviceStore}. This data is used to identify devices on which a user\n * has logged in.\n */\n deviceData: DeviceData\n\n /**\n * The account associated with the device account.\n */\n account: Account\n\n /**\n * The list of clients that are authorized by the account, as created through\n * the {@link AccountStore.setAuthorizedClient} method.\n */\n authorizedClients: AuthorizedClients\n\n /**\n * The date at which the device account was created. This value is currently\n * not used.\n */\n createdAt: Date\n\n /**\n * The date at which the device account was last updated. This value is used\n * to determine the date at which the user last authenticated on a device\n */\n updatedAt: Date\n}\n\nexport type SignUpData = SignUpInput & {\n hcaptchaResult?: HcaptchaVerifyResult\n inviteCode?: InviteCode\n}\n\nexport interface AccountStore {\n /**\n * @throws {HandleUnavailableError} - To indicate that the handle is already taken\n * @throws {InvalidRequestError} - To indicate that some data is invalid\n */\n createAccount(data: CreateAccountData): Awaitable<Account>\n\n /**\n * @throws {InvalidRequestError} - When the credentials are not valid\n * @throws {SecondAuthenticationFactorRequiredError} - To indicate that an {@link SecondAuthenticationFactorRequiredError.type} is required in the credentials\n */\n authenticateAccount(data: AuthenticateAccountData): Awaitable<Account>\n\n /**\n * Add a client & scopes to the list of authorized clients for the given account.\n */\n setAuthorizedClient(\n sub: Sub,\n clientId: ClientId,\n data: AuthorizedClientData,\n ): Awaitable<void>\n\n /**\n * @throws {InvalidRequestError} - When the credentials are not valid\n */\n getAccount(sub: Sub): Awaitable<{\n account: Account\n authorizedClients: AuthorizedClients\n }>\n\n /**\n * @param data.requestId - If provided, the inserted account must be bound to\n * that particular requestId.\n *\n * @note Whenever a particular device account is created, all **unbound**\n * device accounts for the same `deviceId` & `sub` should be deleted.\n *\n * @note When a particular request is deleted (through\n * {@link RequestStore.deleteRequest}), all accounts bound to that request\n * should be deleted as well.\n */\n upsertDeviceAccount(deviceId: DeviceId, sub: Sub): Awaitable<void>\n\n /**\n * @param requestId - If provided, the result must either have the same\n * requestId, or not be bound to a particular requestId. If `null`, the\n * result must not be bound to a particular requestId.\n * @throws {InvalidRequestError} - Instead of returning `null` in order to\n * provide a custom error message\n */\n getDeviceAccount(\n deviceId: DeviceId,\n sub: Sub,\n ): Awaitable<DeviceAccount | null>\n\n /**\n * Removes *all* the unbound device-accounts associated with the given device\n * & account.\n *\n * @note Noop if the device-account is not found.\n */\n removeDeviceAccount(deviceId: DeviceId, sub: Sub): Awaitable<void>\n\n /**\n * @returns **all** the device accounts that match the {@link requestId}\n * criteria and given {@link filter}.\n */\n listDeviceAccounts(\n filter: { sub: Sub } | { deviceId: DeviceId },\n ): Awaitable<DeviceAccount[]>\n\n resetPasswordRequest(\n data: ResetPasswordRequestInput,\n ): Awaitable<null | Account>\n\n resetPasswordConfirm(\n data: ResetPasswordConfirmInput,\n ): Awaitable<null | Account>\n\n /**\n * @throws {HandleUnavailableError} - To indicate that the handle is already taken\n */\n verifyHandleAvailability(handle: string): Awaitable<void>\n}\n\nexport const isAccountStore = buildInterfaceChecker<AccountStore>([\n 'createAccount',\n 'authenticateAccount',\n 'setAuthorizedClient',\n 'getAccount',\n 'upsertDeviceAccount',\n 'getDeviceAccount',\n 'removeDeviceAccount',\n 'listDeviceAccounts',\n 'resetPasswordRequest',\n 'resetPasswordConfirm',\n 'verifyHandleAvailability',\n])\n\nexport function asAccountStore<V>(implementation: V): V & AccountStore {\n if (!implementation || !isAccountStore(implementation)) {\n throw new Error('Invalid AccountStore implementation')\n }\n return implementation\n}\n"]}
1
+ {"version":3,"file":"account-store.js","sourceRoot":"","sources":["../../src/account/account-store.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAiNA,wCAKC;AA5MD,iDAAsE;AACtE,wDAK2B;AAsBzB,uGA1BA,wCAAsB,OA0BA;AACtB,wGA1BA,yCAAuB,OA0BA;AACvB,oGA1BA,qCAAmB,OA0BA;AACnB,wHA1BA,yDAAuC,OA0BA;AApBzC,kEAAkE;AAElE,yDAAsC;AACtC,2DAAwC;AACxC,yDAAsC;AACtC,iDAA8B;AAC9B,2DAAwC;AAwK3B,QAAA,cAAc,GAAG,IAAA,+BAAqB,EAAe;IAChE,eAAe;IACf,qBAAqB;IACrB,qBAAqB;IACrB,YAAY;IACZ,qBAAqB;IACrB,kBAAkB;IAClB,qBAAqB;IACrB,oBAAoB;IACpB,sBAAsB;IACtB,sBAAsB;IACtB,0BAA0B;CAC3B,CAAC,CAAA;AAEF,SAAgB,cAAc,CAAI,cAAiB;IACjD,IAAI,CAAC,cAAc,IAAI,CAAC,IAAA,sBAAc,EAAC,cAAc,CAAC,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;IACxD,CAAC;IACD,OAAO,cAAc,CAAA;AACvB,CAAC","sourcesContent":["import type {\n Account,\n ConfirmResetPasswordInput,\n InitiatePasswordResetInput,\n} from '@atproto/oauth-provider-api'\nimport { OAuthScope } from '@atproto/oauth-types'\nimport { ClientId } from '../client/client-id.js'\nimport { DeviceId } from '../device/device-id.js'\nimport { DeviceData } from '../device/device-store.js'\nimport { HcaptchaVerifyResult } from '../lib/hcaptcha.js'\nimport { Awaitable, buildInterfaceChecker } from '../lib/util/type.js'\nimport {\n HandleUnavailableError,\n InvalidCredentialsError,\n InvalidRequestError,\n SecondAuthenticationFactorRequiredError,\n} from '../oauth-errors.js'\nimport { Sub } from '../oidc/sub.js'\nimport { InviteCode } from '../types/invite-code.js'\nimport { SignUpInput } from './sign-up-input.js'\n\n// Export all types needed to implement the AccountStore interface\n\nexport * from '../client/client-id.js'\nexport * from '../device/device-data.js'\nexport * from '../device/device-id.js'\nexport * from '../oidc/sub.js'\nexport * from '../request/request-id.js'\n\nexport type {\n Account,\n HcaptchaVerifyResult,\n InviteCode,\n OAuthScope,\n SignUpInput,\n}\n\nexport {\n HandleUnavailableError,\n InvalidCredentialsError,\n InvalidRequestError,\n SecondAuthenticationFactorRequiredError,\n}\n\nexport type ResetPasswordRequestInput = InitiatePasswordResetInput\nexport type ResetPasswordConfirmInput = ConfirmResetPasswordInput\n\nexport type CreateAccountData = {\n locale: string\n email: string\n password: string\n handle: string\n inviteCode?: string | undefined\n}\n\nexport type AuthenticateAccountData = {\n locale: string\n password: string\n username: string\n emailOtp?: string | undefined\n}\n\nexport type AuthorizedClientData = { authorizedScopes: readonly string[] }\nexport type AuthorizedClients = Map<ClientId, AuthorizedClientData>\n\nexport type DeviceAccount = {\n deviceId: DeviceId\n\n /**\n * The data associated with the device, created through the\n * {@link DeviceStore}. This data is used to identify devices on which a user\n * has logged in.\n */\n deviceData: DeviceData\n\n /**\n * The account associated with the device account.\n */\n account: Account\n\n /**\n * The list of clients that are authorized by the account, as created through\n * the {@link AccountStore.setAuthorizedClient} method.\n */\n authorizedClients: AuthorizedClients\n\n /**\n * The date at which the device account was created. This value is currently\n * not used.\n */\n createdAt: Date\n\n /**\n * The date at which the device account was last updated. This value is used\n * to determine the date at which the user last authenticated on a device\n */\n updatedAt: Date\n}\n\nexport type SignUpData = SignUpInput & {\n hcaptchaResult?: HcaptchaVerifyResult\n inviteCode?: InviteCode\n}\n\nexport interface AccountStore {\n /**\n * @throws {HandleUnavailableError} - To indicate that the handle is already taken\n * @throws {InvalidRequestError} - To indicate that some data is invalid\n */\n createAccount(data: CreateAccountData): Awaitable<Account>\n\n /**\n * @throws {InvalidCredentialsError} - When the credentials are not valid.\n * Populate {@link InvalidCredentialsError.sub} with the subject identifier\n * when the identifier matched an existing account (e.g. wrong password for\n * a known user); omit it when the identifier was not found. Throwing the\n * generic {@link InvalidRequestError} is also accepted for backward\n * compatibility but prevents the `onSignInFailed` hook from distinguishing\n * the two cases.\n * @throws {SecondAuthenticationFactorRequiredError} - To indicate that an {@link SecondAuthenticationFactorRequiredError.type} is required in the credentials\n */\n authenticateAccount(data: AuthenticateAccountData): Awaitable<Account>\n\n /**\n * Add a client & scopes to the list of authorized clients for the given account.\n */\n setAuthorizedClient(\n sub: Sub,\n clientId: ClientId,\n data: AuthorizedClientData,\n ): Awaitable<void>\n\n /**\n * @throws {InvalidRequestError} - When the credentials are not valid\n */\n getAccount(sub: Sub): Awaitable<{\n account: Account\n authorizedClients: AuthorizedClients\n }>\n\n /**\n * @param data.requestId - If provided, the inserted account must be bound to\n * that particular requestId.\n *\n * @note Whenever a particular device account is created, all **unbound**\n * device accounts for the same `deviceId` & `sub` should be deleted.\n *\n * @note When a particular request is deleted (through\n * {@link RequestStore.deleteRequest}), all accounts bound to that request\n * should be deleted as well.\n */\n upsertDeviceAccount(deviceId: DeviceId, sub: Sub): Awaitable<void>\n\n /**\n * @param requestId - If provided, the result must either have the same\n * requestId, or not be bound to a particular requestId. If `null`, the\n * result must not be bound to a particular requestId.\n * @throws {InvalidRequestError} - Instead of returning `null` in order to\n * provide a custom error message\n */\n getDeviceAccount(\n deviceId: DeviceId,\n sub: Sub,\n ): Awaitable<DeviceAccount | null>\n\n /**\n * Removes *all* the unbound device-accounts associated with the given device\n * & account.\n *\n * @note Noop if the device-account is not found.\n */\n removeDeviceAccount(deviceId: DeviceId, sub: Sub): Awaitable<void>\n\n /**\n * @returns **all** the device accounts that match the {@link requestId}\n * criteria and given {@link filter}.\n */\n listDeviceAccounts(\n filter: { sub: Sub } | { deviceId: DeviceId },\n ): Awaitable<DeviceAccount[]>\n\n resetPasswordRequest(\n data: ResetPasswordRequestInput,\n ): Awaitable<null | Account>\n\n resetPasswordConfirm(\n data: ResetPasswordConfirmInput,\n ): Awaitable<null | Account>\n\n /**\n * @throws {HandleUnavailableError} - To indicate that the handle is already taken\n */\n verifyHandleAvailability(handle: string): Awaitable<void>\n}\n\nexport const isAccountStore = buildInterfaceChecker<AccountStore>([\n 'createAccount',\n 'authenticateAccount',\n 'setAuthorizedClient',\n 'getAccount',\n 'upsertDeviceAccount',\n 'getDeviceAccount',\n 'removeDeviceAccount',\n 'listDeviceAccounts',\n 'resetPasswordRequest',\n 'resetPasswordConfirm',\n 'verifyHandleAvailability',\n])\n\nexport function asAccountStore<V>(implementation: V): V & AccountStore {\n if (!implementation || !isAccountStore(implementation)) {\n throw new Error('Invalid AccountStore implementation')\n }\n return implementation\n}\n"]}
@@ -5,36 +5,45 @@ export declare const brandingSchema: z.ZodObject<{
5
5
  colors: z.ZodOptional<z.ZodObject<z.objectUtil.extendShape<z.objectUtil.extendShape<z.objectUtil.extendShape<{
6
6
  light: z.ZodOptional<z.ZodEffects<z.ZodString, import("../lib/util/color.js").RgbColor, string>>;
7
7
  dark: z.ZodOptional<z.ZodEffects<z.ZodString, import("../lib/util/color.js").RgbColor, string>>;
8
- }, Record<"error" | "success" | "warning" | "primary", z.ZodOptional<z.ZodEffects<z.ZodString, import("../lib/util/color.js").RgbColor, string>>>>, Record<"errorContrast" | "successContrast" | "warningContrast" | "primaryContrast", z.ZodOptional<z.ZodEffects<z.ZodString, import("../lib/util/color.js").RgbColor, string>>>>, Record<"errorHue" | "successHue" | "warningHue" | "primaryHue", z.ZodOptional<z.ZodNumber>>>, "strip", z.ZodTypeAny, {
8
+ contrastSaturation: z.ZodOptional<z.ZodNumber>;
9
+ }, Record<"error" | "success" | "warning" | "primary" | "info", z.ZodOptional<z.ZodEffects<z.ZodString, import("../lib/util/color.js").RgbColor, string>>>>, Record<"errorContrast" | "successContrast" | "warningContrast" | "primaryContrast" | "infoContrast", z.ZodOptional<z.ZodEffects<z.ZodString, import("../lib/util/color.js").RgbColor, string>>>>, Record<"errorHue" | "successHue" | "warningHue" | "primaryHue" | "infoHue", z.ZodOptional<z.ZodNumber>>>, "strip", z.ZodTypeAny, {
9
10
  error?: import("../lib/util/color.js").RgbColor | undefined;
10
11
  success?: import("../lib/util/color.js").RgbColor | undefined;
11
12
  warning?: import("../lib/util/color.js").RgbColor | undefined;
12
13
  light?: import("../lib/util/color.js").RgbColor | undefined;
13
14
  dark?: import("../lib/util/color.js").RgbColor | undefined;
15
+ contrastSaturation?: number | undefined;
14
16
  primary?: import("../lib/util/color.js").RgbColor | undefined;
17
+ info?: import("../lib/util/color.js").RgbColor | undefined;
15
18
  errorContrast?: import("../lib/util/color.js").RgbColor | undefined;
16
19
  successContrast?: import("../lib/util/color.js").RgbColor | undefined;
17
20
  warningContrast?: import("../lib/util/color.js").RgbColor | undefined;
18
21
  primaryContrast?: import("../lib/util/color.js").RgbColor | undefined;
22
+ infoContrast?: import("../lib/util/color.js").RgbColor | undefined;
19
23
  errorHue?: number | undefined;
20
24
  successHue?: number | undefined;
21
25
  warningHue?: number | undefined;
22
26
  primaryHue?: number | undefined;
27
+ infoHue?: number | undefined;
23
28
  }, {
24
29
  error?: string | undefined;
25
30
  success?: string | undefined;
26
31
  warning?: string | undefined;
27
32
  light?: string | undefined;
28
33
  dark?: string | undefined;
34
+ contrastSaturation?: number | undefined;
29
35
  primary?: string | undefined;
36
+ info?: string | undefined;
30
37
  errorContrast?: string | undefined;
31
38
  successContrast?: string | undefined;
32
39
  warningContrast?: string | undefined;
33
40
  primaryContrast?: string | undefined;
41
+ infoContrast?: string | undefined;
34
42
  errorHue?: number | undefined;
35
43
  successHue?: number | undefined;
36
44
  warningHue?: number | undefined;
37
45
  primaryHue?: number | undefined;
46
+ infoHue?: number | undefined;
38
47
  }>>;
39
48
  links: z.ZodOptional<z.ZodArray<z.ZodObject<{
40
49
  title: z.ZodUnion<[z.ZodString, z.ZodRecord<z.ZodString, z.ZodOptional<z.ZodString>>]>;
@@ -58,15 +67,19 @@ export declare const brandingSchema: z.ZodObject<{
58
67
  warning?: import("../lib/util/color.js").RgbColor | undefined;
59
68
  light?: import("../lib/util/color.js").RgbColor | undefined;
60
69
  dark?: import("../lib/util/color.js").RgbColor | undefined;
70
+ contrastSaturation?: number | undefined;
61
71
  primary?: import("../lib/util/color.js").RgbColor | undefined;
72
+ info?: import("../lib/util/color.js").RgbColor | undefined;
62
73
  errorContrast?: import("../lib/util/color.js").RgbColor | undefined;
63
74
  successContrast?: import("../lib/util/color.js").RgbColor | undefined;
64
75
  warningContrast?: import("../lib/util/color.js").RgbColor | undefined;
65
76
  primaryContrast?: import("../lib/util/color.js").RgbColor | undefined;
77
+ infoContrast?: import("../lib/util/color.js").RgbColor | undefined;
66
78
  errorHue?: number | undefined;
67
79
  successHue?: number | undefined;
68
80
  warningHue?: number | undefined;
69
81
  primaryHue?: number | undefined;
82
+ infoHue?: number | undefined;
70
83
  } | undefined;
71
84
  links?: {
72
85
  href: string;
@@ -82,15 +95,19 @@ export declare const brandingSchema: z.ZodObject<{
82
95
  warning?: string | undefined;
83
96
  light?: string | undefined;
84
97
  dark?: string | undefined;
98
+ contrastSaturation?: number | undefined;
85
99
  primary?: string | undefined;
100
+ info?: string | undefined;
86
101
  errorContrast?: string | undefined;
87
102
  successContrast?: string | undefined;
88
103
  warningContrast?: string | undefined;
89
104
  primaryContrast?: string | undefined;
105
+ infoContrast?: string | undefined;
90
106
  errorHue?: number | undefined;
91
107
  successHue?: number | undefined;
92
108
  warningHue?: number | undefined;
93
109
  primaryHue?: number | undefined;
110
+ infoHue?: number | undefined;
94
111
  } | undefined;
95
112
  links?: {
96
113
  href: string;
@@ -1 +1 @@
1
- {"version":3,"file":"branding.d.ts","sourceRoot":"","sources":["../../src/customization/branding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAKzB,CAAA;AACF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAC1D,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA"}
1
+ {"version":3,"file":"branding.d.ts","sourceRoot":"","sources":["../../src/customization/branding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAKzB,CAAA;AACF,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA;AAC1D,MAAM,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"build-customization-css.d.ts","sourceRoot":"","sources":["../../src/customization/build-customization-css.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElD,wBAAgB,qBAAqB,CAAC,EACpC,QAAQ,GACT,EAAE,aAAa,GAAG,SAAS,GAAG,MAAM,CAGpC"}
1
+ {"version":3,"file":"build-customization-css.d.ts","sourceRoot":"","sources":["../../src/customization/build-customization-css.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElD,wBAAgB,qBAAqB,CAAC,EACpC,QAAQ,GACT,EAAE,aAAa,GAAG,SAAS,GAAG,MAAM,CAGpC"}
@@ -10,8 +10,22 @@ function buildCustomizationCss({ branding, }) {
10
10
  }
11
11
  function* buildCustomizationVars(branding) {
12
12
  if (branding?.colors) {
13
- const contrastLight = branding.colors.light ?? { r: 255, g: 255, b: 255 };
14
- const contrastDark = branding.colors.dark ?? { r: 0, g: 0, b: 0 };
13
+ const contrastSaturation = branding.colors.contrastSaturation ?? 30;
14
+ yield `--contrast-sat: ${contrastSaturation.toFixed(2)}%;`;
15
+ const contrastLight = branding.colors.light ??
16
+ // Corresponds to color-contrast-975
17
+ (0, color_js_1.hslToRgb)({
18
+ h: branding.colors.primaryHue ?? 0,
19
+ s: contrastSaturation / 100,
20
+ l: 0.07,
21
+ });
22
+ const contrastDark = branding.colors.dark ??
23
+ // Corresponds to color-contrast-25
24
+ (0, color_js_1.hslToRgb)({
25
+ h: branding.colors.primaryHue ?? 0,
26
+ s: contrastSaturation / 100,
27
+ l: 0.953,
28
+ });
15
29
  for (const name of colors_js_1.COLOR_NAMES) {
16
30
  const value = branding.colors[name];
17
31
  if (!value)
@@ -1 +1 @@
1
- {"version":3,"file":"build-customization-css.js","sourceRoot":"","sources":["../../src/customization/build-customization-css.ts"],"names":[],"mappings":";;AAKA,sDAKC;AAVD,mDAAoE;AAEpE,2CAAyC;AAGzC,SAAgB,qBAAqB,CAAC,EACpC,QAAQ,GACM;IACd,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC,QAAQ,CAAC,CAAC,CAAA;IACzD,IAAI,IAAI,CAAC,MAAM;QAAE,OAAO,WAAW,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAA;AACvD,CAAC;AAED,QAAQ,CAAC,CAAC,sBAAsB,CAAC,QAAmB;IAClD,IAAI,QAAQ,EAAE,MAAM,EAAE,CAAC;QACrB,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAA;QACzE,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAA;QAEjE,KAAK,MAAM,IAAI,IAAI,uBAAW,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;YACnC,IAAI,CAAC,KAAK;gBAAE,SAAQ,CAAC,sBAAsB;YAE3C,MAAM,QAAQ,GACZ,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,UAAU,CAAC;gBAClC,IAAA,4BAAiB,EAAC,KAAK,EAAE,aAAa,EAAE,YAAY,CAAC,CAAA;YAEvD,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,KAAK,CAAC,IAAI,IAAA,qBAAU,EAAC,KAAK,CAAC,CAAA;YAE9D,MAAM,oBAAoB,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,GAAG,CAAA;YACnE,MAAM,oBAAoB,IAAI,cAAc,QAAQ,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,GAAG,CAAA;YACrF,MAAM,oBAAoB,IAAI,SAAS,GAAG,GAAG,CAAA;QAC/C,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["import { extractHue, pickContrastColor } from '../lib/util/color.js'\nimport { Branding } from './branding.js'\nimport { COLOR_NAMES } from './colors.js'\nimport { Customization } from './customization.js'\n\nexport function buildCustomizationCss({\n branding,\n}: Customization): undefined | string {\n const vars = Array.from(buildCustomizationVars(branding))\n if (vars.length) return `:root { ${vars.join(' ')} }`\n}\n\nfunction* buildCustomizationVars(branding?: Branding): Generator<string> {\n if (branding?.colors) {\n const contrastLight = branding.colors.light ?? { r: 255, g: 255, b: 255 }\n const contrastDark = branding.colors.dark ?? { r: 0, g: 0, b: 0 }\n\n for (const name of COLOR_NAMES) {\n const value = branding.colors[name]\n if (!value) continue // Skip missing colors\n\n const contrast =\n branding.colors[`${name}Contrast`] ??\n pickContrastColor(value, contrastLight, contrastDark)\n\n const hue = branding.colors[`${name}Hue`] ?? extractHue(value)\n\n yield `--branding-color-${name}: ${value.r} ${value.g} ${value.b};`\n yield `--branding-color-${name}-contrast: ${contrast.r} ${contrast.g} ${contrast.b};`\n yield `--branding-color-${name}-hue: ${hue};`\n }\n }\n}\n"]}
1
+ {"version":3,"file":"build-customization-css.js","sourceRoot":"","sources":["../../src/customization/build-customization-css.ts"],"names":[],"mappings":";;AAUA,sDAKC;AAfD,mDAK6B;AAE7B,2CAAyC;AAGzC,SAAgB,qBAAqB,CAAC,EACpC,QAAQ,GACM;IACd,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC,QAAQ,CAAC,CAAC,CAAA;IACzD,IAAI,IAAI,CAAC,MAAM;QAAE,OAAO,WAAW,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAA;AACvD,CAAC;AAED,QAAQ,CAAC,CAAC,sBAAsB,CAAC,QAAmB;IAClD,IAAI,QAAQ,EAAE,MAAM,EAAE,CAAC;QACrB,MAAM,kBAAkB,GAAG,QAAQ,CAAC,MAAM,CAAC,kBAAkB,IAAI,EAAE,CAAA;QACnE,MAAM,mBAAmB,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA;QAE1D,MAAM,aAAa,GACjB,QAAQ,CAAC,MAAM,CAAC,KAAK;YACrB,oCAAoC;YACpC,IAAA,mBAAQ,EAAC;gBACP,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC;gBAClC,CAAC,EAAE,kBAAkB,GAAG,GAAG;gBAC3B,CAAC,EAAE,IAAI;aACR,CAAC,CAAA;QACJ,MAAM,YAAY,GAChB,QAAQ,CAAC,MAAM,CAAC,IAAI;YACpB,mCAAmC;YACnC,IAAA,mBAAQ,EAAC;gBACP,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,IAAI,CAAC;gBAClC,CAAC,EAAE,kBAAkB,GAAG,GAAG;gBAC3B,CAAC,EAAE,KAAK;aACT,CAAC,CAAA;QAEJ,KAAK,MAAM,IAAI,IAAI,uBAAW,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;YACnC,IAAI,CAAC,KAAK;gBAAE,SAAQ,CAAC,sBAAsB;YAE3C,MAAM,QAAQ,GACZ,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,UAAU,CAAC;gBAClC,IAAA,4BAAiB,EAAC,KAAK,EAAE,aAAa,EAAE,YAAY,CAAC,CAAA;YAEvD,MAAM,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,KAAK,CAAC,IAAI,IAAA,qBAAU,EAAC,KAAK,CAAC,CAAA;YAE9D,MAAM,oBAAoB,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,GAAG,CAAA;YACnE,MAAM,oBAAoB,IAAI,cAAc,QAAQ,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,GAAG,CAAA;YACrF,MAAM,oBAAoB,IAAI,SAAS,GAAG,GAAG,CAAA;QAC/C,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["import {\n RgbColor,\n extractHue,\n hslToRgb,\n pickContrastColor,\n} from '../lib/util/color.js'\nimport { Branding } from './branding.js'\nimport { COLOR_NAMES } from './colors.js'\nimport { Customization } from './customization.js'\n\nexport function buildCustomizationCss({\n branding,\n}: Customization): undefined | string {\n const vars = Array.from(buildCustomizationVars(branding))\n if (vars.length) return `:root { ${vars.join(' ')} }`\n}\n\nfunction* buildCustomizationVars(branding?: Branding): Generator<string> {\n if (branding?.colors) {\n const contrastSaturation = branding.colors.contrastSaturation ?? 30\n yield `--contrast-sat: ${contrastSaturation.toFixed(2)}%;`\n\n const contrastLight: RgbColor =\n branding.colors.light ??\n // Corresponds to color-contrast-975\n hslToRgb({\n h: branding.colors.primaryHue ?? 0,\n s: contrastSaturation / 100,\n l: 0.07,\n })\n const contrastDark: RgbColor =\n branding.colors.dark ??\n // Corresponds to color-contrast-25\n hslToRgb({\n h: branding.colors.primaryHue ?? 0,\n s: contrastSaturation / 100,\n l: 0.953,\n })\n\n for (const name of COLOR_NAMES) {\n const value = branding.colors[name]\n if (!value) continue // Skip missing colors\n\n const contrast =\n branding.colors[`${name}Contrast`] ??\n pickContrastColor(value, contrastLight, contrastDark)\n\n const hue = branding.colors[`${name}Hue`] ?? extractHue(value)\n\n yield `--branding-color-${name}: ${value.r} ${value.g} ${value.b};`\n yield `--branding-color-${name}-contrast: ${contrast.r} ${contrast.g} ${contrast.b};`\n yield `--branding-color-${name}-hue: ${hue};`\n }\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { CustomizationData } from '@atproto/oauth-provider-api';
2
- import { Customization } from './customization.js';
1
+ import type { CustomizationData } from '@atproto/oauth-provider-api';
2
+ import type { Customization } from './customization.js';
3
3
  export declare function buildCustomizationData({ branding, availableUserDomains, inviteCodeRequired, hcaptcha, }: Customization): CustomizationData;
4
4
  //# sourceMappingURL=build-customization-data.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-customization-data.d.ts","sourceRoot":"","sources":["../../src/customization/build-customization-data.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElD,wBAAgB,sBAAsB,CAAC,EACrC,QAAQ,EACR,oBAAoB,EACpB,kBAAkB,EAClB,QAAQ,GACT,EAAE,aAAa,GAAG,iBAAiB,CAanC"}
1
+ {"version":3,"file":"build-customization-data.d.ts","sourceRoot":"","sources":["../../src/customization/build-customization-data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AACpE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAEvD,wBAAgB,sBAAsB,CAAC,EACrC,QAAQ,EACR,oBAAoB,EACpB,kBAAkB,EAClB,QAAQ,GACT,EAAE,aAAa,GAAG,iBAAiB,CAanC"}
@@ -1 +1 @@
1
- {"version":3,"file":"build-customization-data.js","sourceRoot":"","sources":["../../src/customization/build-customization-data.ts"],"names":[],"mappings":";;AAGA,wDAkBC;AAlBD,SAAgB,sBAAsB,CAAC,EACrC,QAAQ,EACR,oBAAoB,EACpB,kBAAkB,EAClB,QAAQ,GACM;IACd,4EAA4E;IAC5E,iBAAiB;IACjB,6EAA6E;IAC7E,6DAA6D;IAC7D,OAAO;QACL,oBAAoB;QACpB,kBAAkB;QAClB,eAAe,EAAE,QAAQ,EAAE,OAAO;QAClC,IAAI,EAAE,QAAQ,EAAE,IAAI;QACpB,IAAI,EAAE,QAAQ,EAAE,IAAI;QACpB,KAAK,EAAE,QAAQ,EAAE,KAAK;KACvB,CAAA;AACH,CAAC","sourcesContent":["import { CustomizationData } from '@atproto/oauth-provider-api'\nimport { Customization } from './customization.js'\n\nexport function buildCustomizationData({\n branding,\n availableUserDomains,\n inviteCodeRequired,\n hcaptcha,\n}: Customization): CustomizationData {\n // @NOTE the front end does not need colors here as they will be injected as\n // CSS variables.\n // @NOTE We only copy the values explicitly needed to avoid leaking sensitive\n // data (in case the caller passed more than what we expect).\n return {\n availableUserDomains,\n inviteCodeRequired,\n hcaptchaSiteKey: hcaptcha?.siteKey,\n name: branding?.name,\n logo: branding?.logo,\n links: branding?.links,\n }\n}\n"]}
1
+ {"version":3,"file":"build-customization-data.js","sourceRoot":"","sources":["../../src/customization/build-customization-data.ts"],"names":[],"mappings":";;AAGA,wDAkBC;AAlBD,SAAgB,sBAAsB,CAAC,EACrC,QAAQ,EACR,oBAAoB,EACpB,kBAAkB,EAClB,QAAQ,GACM;IACd,4EAA4E;IAC5E,iBAAiB;IACjB,6EAA6E;IAC7E,6DAA6D;IAC7D,OAAO;QACL,oBAAoB;QACpB,kBAAkB;QAClB,eAAe,EAAE,QAAQ,EAAE,OAAO;QAClC,IAAI,EAAE,QAAQ,EAAE,IAAI;QACpB,IAAI,EAAE,QAAQ,EAAE,IAAI;QACpB,KAAK,EAAE,QAAQ,EAAE,KAAK;KACvB,CAAA;AACH,CAAC","sourcesContent":["import type { CustomizationData } from '@atproto/oauth-provider-api'\nimport type { Customization } from './customization.js'\n\nexport function buildCustomizationData({\n branding,\n availableUserDomains,\n inviteCodeRequired,\n hcaptcha,\n}: Customization): CustomizationData {\n // @NOTE the front end does not need colors here as they will be injected as\n // CSS variables.\n // @NOTE We only copy the values explicitly needed to avoid leaking sensitive\n // data (in case the caller passed more than what we expect).\n return {\n availableUserDomains,\n inviteCodeRequired,\n hcaptchaSiteKey: hcaptcha?.siteKey,\n name: branding?.name,\n logo: branding?.logo,\n links: branding?.links,\n }\n}\n"]}
@@ -1,39 +1,48 @@
1
1
  import { z } from 'zod';
2
- export declare const COLOR_NAMES: readonly ["primary", "error", "warning", "success"];
2
+ export declare const COLOR_NAMES: readonly ["primary", "error", "warning", "info", "success"];
3
3
  export type ColorName = (typeof COLOR_NAMES)[number];
4
4
  export declare const colorsSchema: z.ZodObject<z.objectUtil.extendShape<z.objectUtil.extendShape<z.objectUtil.extendShape<{
5
5
  light: z.ZodOptional<z.ZodEffects<z.ZodString, import("../lib/util/color.js").RgbColor, string>>;
6
6
  dark: z.ZodOptional<z.ZodEffects<z.ZodString, import("../lib/util/color.js").RgbColor, string>>;
7
- }, Record<"error" | "success" | "warning" | "primary", z.ZodOptional<z.ZodEffects<z.ZodString, import("../lib/util/color.js").RgbColor, string>>>>, Record<"errorContrast" | "successContrast" | "warningContrast" | "primaryContrast", z.ZodOptional<z.ZodEffects<z.ZodString, import("../lib/util/color.js").RgbColor, string>>>>, Record<"errorHue" | "successHue" | "warningHue" | "primaryHue", z.ZodOptional<z.ZodNumber>>>, "strip", z.ZodTypeAny, {
7
+ contrastSaturation: z.ZodOptional<z.ZodNumber>;
8
+ }, Record<"error" | "success" | "warning" | "primary" | "info", z.ZodOptional<z.ZodEffects<z.ZodString, import("../lib/util/color.js").RgbColor, string>>>>, Record<"errorContrast" | "successContrast" | "warningContrast" | "primaryContrast" | "infoContrast", z.ZodOptional<z.ZodEffects<z.ZodString, import("../lib/util/color.js").RgbColor, string>>>>, Record<"errorHue" | "successHue" | "warningHue" | "primaryHue" | "infoHue", z.ZodOptional<z.ZodNumber>>>, "strip", z.ZodTypeAny, {
8
9
  error?: import("../lib/util/color.js").RgbColor | undefined;
9
10
  success?: import("../lib/util/color.js").RgbColor | undefined;
10
11
  warning?: import("../lib/util/color.js").RgbColor | undefined;
11
12
  light?: import("../lib/util/color.js").RgbColor | undefined;
12
13
  dark?: import("../lib/util/color.js").RgbColor | undefined;
14
+ contrastSaturation?: number | undefined;
13
15
  primary?: import("../lib/util/color.js").RgbColor | undefined;
16
+ info?: import("../lib/util/color.js").RgbColor | undefined;
14
17
  errorContrast?: import("../lib/util/color.js").RgbColor | undefined;
15
18
  successContrast?: import("../lib/util/color.js").RgbColor | undefined;
16
19
  warningContrast?: import("../lib/util/color.js").RgbColor | undefined;
17
20
  primaryContrast?: import("../lib/util/color.js").RgbColor | undefined;
21
+ infoContrast?: import("../lib/util/color.js").RgbColor | undefined;
18
22
  errorHue?: number | undefined;
19
23
  successHue?: number | undefined;
20
24
  warningHue?: number | undefined;
21
25
  primaryHue?: number | undefined;
26
+ infoHue?: number | undefined;
22
27
  }, {
23
28
  error?: string | undefined;
24
29
  success?: string | undefined;
25
30
  warning?: string | undefined;
26
31
  light?: string | undefined;
27
32
  dark?: string | undefined;
33
+ contrastSaturation?: number | undefined;
28
34
  primary?: string | undefined;
35
+ info?: string | undefined;
29
36
  errorContrast?: string | undefined;
30
37
  successContrast?: string | undefined;
31
38
  warningContrast?: string | undefined;
32
39
  primaryContrast?: string | undefined;
40
+ infoContrast?: string | undefined;
33
41
  errorHue?: number | undefined;
34
42
  successHue?: number | undefined;
35
43
  warningHue?: number | undefined;
36
44
  primaryHue?: number | undefined;
45
+ infoHue?: number | undefined;
37
46
  }>;
38
47
  export type Colors = z.infer<typeof colorsSchema>;
39
48
  //# sourceMappingURL=colors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../../src/customization/colors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,WAAW,qDAAsD,CAAA;AAC9E,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAA;AAEpD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmBtB,CAAA;AAEH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAA"}
1
+ {"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../../src/customization/colors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,WAAW,6DAMd,CAAA;AACV,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAA;AAEpD,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgCtB,CAAA;AAEH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAA"}
@@ -4,11 +4,29 @@ exports.colorsSchema = exports.COLOR_NAMES = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const color_hue_js_1 = require("../types/color-hue.js");
6
6
  const rgb_color_js_1 = require("../types/rgb-color.js");
7
- exports.COLOR_NAMES = ['primary', 'error', 'warning', 'success'];
7
+ exports.COLOR_NAMES = [
8
+ 'primary',
9
+ 'error',
10
+ 'warning',
11
+ 'info',
12
+ 'success',
13
+ ];
8
14
  exports.colorsSchema = zod_1.z
9
15
  .object({
16
+ // The "light" and "dark" colors are used as default for unspecified
17
+ // contrast colors. The color that has the highest contrast ratio with the
18
+ // color base will be used. e.G. If "primary" is specified but
19
+ // "primaryContrast" is not, then the contrast color will be either "light"
20
+ // or "dark" depending on which one has the highest contrast ratio with
21
+ // "primary".
10
22
  light: rgb_color_js_1.rgbColorSchema.optional(),
11
23
  dark: rgb_color_js_1.rgbColorSchema.optional(),
24
+ // The "contrastSaturation" is used to compute the saturation of the
25
+ // "contrast" color. The "contrast" color is a (dynamic) color derived from
26
+ // the "primaryHue" color with the specified saturation and a variable
27
+ // lightness. "color-contrast-900" is used for default text, while
28
+ // "color-contrast-0" is used for the page background.
29
+ contrastSaturation: zod_1.z.number().min(0).max(100).optional(),
12
30
  })
13
31
  .extend(Object.fromEntries(exports.COLOR_NAMES.map((name) => [name, rgb_color_js_1.rgbColorSchema.optional()])))
14
32
  .extend(Object.fromEntries(exports.COLOR_NAMES.map((name) => [`${name}Contrast`, rgb_color_js_1.rgbColorSchema.optional()])))
@@ -1 +1 @@
1
- {"version":3,"file":"colors.js","sourceRoot":"","sources":["../../src/customization/colors.ts"],"names":[],"mappings":";;;AAAA,6BAAuB;AACvB,wDAAsD;AACtD,wDAAsD;AAEzC,QAAA,WAAW,GAAG,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,CAAU,CAAA;AAGjE,QAAA,YAAY,GAAG,OAAC;KAC1B,MAAM,CAAC;IACN,KAAK,EAAE,6BAAc,CAAC,QAAQ,EAAE;IAChC,IAAI,EAAE,6BAAc,CAAC,QAAQ,EAAE;CAChC,CAAC;KACD,MAAM,CACL,MAAM,CAAC,WAAW,CAChB,mBAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,6BAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,CACF,CAC7D;KACA,MAAM,CACL,MAAM,CAAC,WAAW,CAChB,mBAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,UAAU,EAAE,6BAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,CACF,CAC1E;KACA,MAAM,CACL,MAAM,CAAC,WAAW,CAChB,mBAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,KAAK,EAAE,6BAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,CACF,CACrE,CAAA","sourcesContent":["import { z } from 'zod'\nimport { colorHueSchema } from '../types/color-hue.js'\nimport { rgbColorSchema } from '../types/rgb-color.js'\n\nexport const COLOR_NAMES = ['primary', 'error', 'warning', 'success'] as const\nexport type ColorName = (typeof COLOR_NAMES)[number]\n\nexport const colorsSchema = z\n .object({\n light: rgbColorSchema.optional(),\n dark: rgbColorSchema.optional(),\n })\n .extend(\n Object.fromEntries(\n COLOR_NAMES.map((name) => [name, rgbColorSchema.optional()]),\n ) as Record<ColorName, z.ZodOptional<typeof rgbColorSchema>>,\n )\n .extend(\n Object.fromEntries(\n COLOR_NAMES.map((name) => [`${name}Contrast`, rgbColorSchema.optional()]),\n ) as Record<`${ColorName}Contrast`, z.ZodOptional<typeof rgbColorSchema>>,\n )\n .extend(\n Object.fromEntries(\n COLOR_NAMES.map((name) => [`${name}Hue`, colorHueSchema.optional()]),\n ) as Record<`${ColorName}Hue`, z.ZodOptional<typeof colorHueSchema>>,\n )\n\nexport type Colors = z.infer<typeof colorsSchema>\n"]}
1
+ {"version":3,"file":"colors.js","sourceRoot":"","sources":["../../src/customization/colors.ts"],"names":[],"mappings":";;;AAAA,6BAAuB;AACvB,wDAAsD;AACtD,wDAAsD;AAEzC,QAAA,WAAW,GAAG;IACzB,SAAS;IACT,OAAO;IACP,SAAS;IACT,MAAM;IACN,SAAS;CACD,CAAA;AAGG,QAAA,YAAY,GAAG,OAAC;KAC1B,MAAM,CAAC;IACN,oEAAoE;IACpE,0EAA0E;IAC1E,8DAA8D;IAC9D,2EAA2E;IAC3E,uEAAuE;IACvE,aAAa;IACb,KAAK,EAAE,6BAAc,CAAC,QAAQ,EAAE;IAChC,IAAI,EAAE,6BAAc,CAAC,QAAQ,EAAE;IAE/B,oEAAoE;IACpE,2EAA2E;IAC3E,sEAAsE;IACtE,kEAAkE;IAClE,sDAAsD;IACtD,kBAAkB,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;CAC1D,CAAC;KACD,MAAM,CACL,MAAM,CAAC,WAAW,CAChB,mBAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,6BAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,CACF,CAC7D;KACA,MAAM,CACL,MAAM,CAAC,WAAW,CAChB,mBAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,UAAU,EAAE,6BAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,CACF,CAC1E;KACA,MAAM,CACL,MAAM,CAAC,WAAW,CAChB,mBAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,KAAK,EAAE,6BAAc,CAAC,QAAQ,EAAE,CAAC,CAAC,CACF,CACrE,CAAA","sourcesContent":["import { z } from 'zod'\nimport { colorHueSchema } from '../types/color-hue.js'\nimport { rgbColorSchema } from '../types/rgb-color.js'\n\nexport const COLOR_NAMES = [\n 'primary',\n 'error',\n 'warning',\n 'info',\n 'success',\n] as const\nexport type ColorName = (typeof COLOR_NAMES)[number]\n\nexport const colorsSchema = z\n .object({\n // The \"light\" and \"dark\" colors are used as default for unspecified\n // contrast colors. The color that has the highest contrast ratio with the\n // color base will be used. e.G. If \"primary\" is specified but\n // \"primaryContrast\" is not, then the contrast color will be either \"light\"\n // or \"dark\" depending on which one has the highest contrast ratio with\n // \"primary\".\n light: rgbColorSchema.optional(),\n dark: rgbColorSchema.optional(),\n\n // The \"contrastSaturation\" is used to compute the saturation of the\n // \"contrast\" color. The \"contrast\" color is a (dynamic) color derived from\n // the \"primaryHue\" color with the specified saturation and a variable\n // lightness. \"color-contrast-900\" is used for default text, while\n // \"color-contrast-0\" is used for the page background.\n contrastSaturation: z.number().min(0).max(100).optional(),\n })\n .extend(\n Object.fromEntries(\n COLOR_NAMES.map((name) => [name, rgbColorSchema.optional()]),\n ) as Record<ColorName, z.ZodOptional<typeof rgbColorSchema>>,\n )\n .extend(\n Object.fromEntries(\n COLOR_NAMES.map((name) => [`${name}Contrast`, rgbColorSchema.optional()]),\n ) as Record<`${ColorName}Contrast`, z.ZodOptional<typeof rgbColorSchema>>,\n )\n .extend(\n Object.fromEntries(\n COLOR_NAMES.map((name) => [`${name}Hue`, colorHueSchema.optional()]),\n ) as Record<`${ColorName}Hue`, z.ZodOptional<typeof colorHueSchema>>,\n )\n\nexport type Colors = z.infer<typeof colorsSchema>\n"]}