@_mustachio/openauth 0.6.0

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 (192) hide show
  1. package/dist/esm/client.js +186 -0
  2. package/dist/esm/css.d.js +0 -0
  3. package/dist/esm/error.js +73 -0
  4. package/dist/esm/index.js +14 -0
  5. package/dist/esm/issuer.js +558 -0
  6. package/dist/esm/jwt.js +16 -0
  7. package/dist/esm/keys.js +113 -0
  8. package/dist/esm/pkce.js +35 -0
  9. package/dist/esm/provider/apple.js +28 -0
  10. package/dist/esm/provider/arctic.js +43 -0
  11. package/dist/esm/provider/code.js +58 -0
  12. package/dist/esm/provider/cognito.js +16 -0
  13. package/dist/esm/provider/discord.js +15 -0
  14. package/dist/esm/provider/facebook.js +24 -0
  15. package/dist/esm/provider/github.js +15 -0
  16. package/dist/esm/provider/google.js +25 -0
  17. package/dist/esm/provider/index.js +3 -0
  18. package/dist/esm/provider/jumpcloud.js +15 -0
  19. package/dist/esm/provider/keycloak.js +15 -0
  20. package/dist/esm/provider/linkedin.js +15 -0
  21. package/dist/esm/provider/m2m.js +17 -0
  22. package/dist/esm/provider/microsoft.js +24 -0
  23. package/dist/esm/provider/oauth2.js +119 -0
  24. package/dist/esm/provider/oidc.js +69 -0
  25. package/dist/esm/provider/passkey.js +315 -0
  26. package/dist/esm/provider/password.js +306 -0
  27. package/dist/esm/provider/provider.js +10 -0
  28. package/dist/esm/provider/slack.js +15 -0
  29. package/dist/esm/provider/spotify.js +15 -0
  30. package/dist/esm/provider/twitch.js +15 -0
  31. package/dist/esm/provider/x.js +16 -0
  32. package/dist/esm/provider/yahoo.js +15 -0
  33. package/dist/esm/random.js +27 -0
  34. package/dist/esm/storage/aws.js +39 -0
  35. package/dist/esm/storage/cloudflare.js +42 -0
  36. package/dist/esm/storage/dynamo.js +116 -0
  37. package/dist/esm/storage/memory.js +88 -0
  38. package/dist/esm/storage/storage.js +36 -0
  39. package/dist/esm/subject.js +7 -0
  40. package/dist/esm/ui/base.js +407 -0
  41. package/dist/esm/ui/code.js +151 -0
  42. package/dist/esm/ui/form.js +43 -0
  43. package/dist/esm/ui/icon.js +92 -0
  44. package/dist/esm/ui/passkey.js +329 -0
  45. package/dist/esm/ui/password.js +338 -0
  46. package/dist/esm/ui/select.js +187 -0
  47. package/dist/esm/ui/theme.js +115 -0
  48. package/dist/esm/util.js +54 -0
  49. package/dist/types/client.d.ts +466 -0
  50. package/dist/types/client.d.ts.map +1 -0
  51. package/dist/types/error.d.ts +77 -0
  52. package/dist/types/error.d.ts.map +1 -0
  53. package/dist/types/index.d.ts +20 -0
  54. package/dist/types/index.d.ts.map +1 -0
  55. package/dist/types/issuer.d.ts +465 -0
  56. package/dist/types/issuer.d.ts.map +1 -0
  57. package/dist/types/jwt.d.ts +6 -0
  58. package/dist/types/jwt.d.ts.map +1 -0
  59. package/dist/types/keys.d.ts +18 -0
  60. package/dist/types/keys.d.ts.map +1 -0
  61. package/dist/types/pkce.d.ts +7 -0
  62. package/dist/types/pkce.d.ts.map +1 -0
  63. package/dist/types/provider/apple.d.ts +108 -0
  64. package/dist/types/provider/apple.d.ts.map +1 -0
  65. package/dist/types/provider/arctic.d.ts +16 -0
  66. package/dist/types/provider/arctic.d.ts.map +1 -0
  67. package/dist/types/provider/code.d.ts +74 -0
  68. package/dist/types/provider/code.d.ts.map +1 -0
  69. package/dist/types/provider/cognito.d.ts +64 -0
  70. package/dist/types/provider/cognito.d.ts.map +1 -0
  71. package/dist/types/provider/discord.d.ts +38 -0
  72. package/dist/types/provider/discord.d.ts.map +1 -0
  73. package/dist/types/provider/facebook.d.ts +74 -0
  74. package/dist/types/provider/facebook.d.ts.map +1 -0
  75. package/dist/types/provider/github.d.ts +38 -0
  76. package/dist/types/provider/github.d.ts.map +1 -0
  77. package/dist/types/provider/google.d.ts +74 -0
  78. package/dist/types/provider/google.d.ts.map +1 -0
  79. package/dist/types/provider/index.d.ts +4 -0
  80. package/dist/types/provider/index.d.ts.map +1 -0
  81. package/dist/types/provider/jumpcloud.d.ts +38 -0
  82. package/dist/types/provider/jumpcloud.d.ts.map +1 -0
  83. package/dist/types/provider/keycloak.d.ts +67 -0
  84. package/dist/types/provider/keycloak.d.ts.map +1 -0
  85. package/dist/types/provider/linkedin.d.ts +6 -0
  86. package/dist/types/provider/linkedin.d.ts.map +1 -0
  87. package/dist/types/provider/m2m.d.ts +34 -0
  88. package/dist/types/provider/m2m.d.ts.map +1 -0
  89. package/dist/types/provider/microsoft.d.ts +89 -0
  90. package/dist/types/provider/microsoft.d.ts.map +1 -0
  91. package/dist/types/provider/oauth2.d.ts +133 -0
  92. package/dist/types/provider/oauth2.d.ts.map +1 -0
  93. package/dist/types/provider/oidc.d.ts +91 -0
  94. package/dist/types/provider/oidc.d.ts.map +1 -0
  95. package/dist/types/provider/passkey.d.ts +143 -0
  96. package/dist/types/provider/passkey.d.ts.map +1 -0
  97. package/dist/types/provider/password.d.ts +210 -0
  98. package/dist/types/provider/password.d.ts.map +1 -0
  99. package/dist/types/provider/provider.d.ts +29 -0
  100. package/dist/types/provider/provider.d.ts.map +1 -0
  101. package/dist/types/provider/slack.d.ts +59 -0
  102. package/dist/types/provider/slack.d.ts.map +1 -0
  103. package/dist/types/provider/spotify.d.ts +38 -0
  104. package/dist/types/provider/spotify.d.ts.map +1 -0
  105. package/dist/types/provider/twitch.d.ts +38 -0
  106. package/dist/types/provider/twitch.d.ts.map +1 -0
  107. package/dist/types/provider/x.d.ts +38 -0
  108. package/dist/types/provider/x.d.ts.map +1 -0
  109. package/dist/types/provider/yahoo.d.ts +38 -0
  110. package/dist/types/provider/yahoo.d.ts.map +1 -0
  111. package/dist/types/random.d.ts +3 -0
  112. package/dist/types/random.d.ts.map +1 -0
  113. package/dist/types/storage/aws.d.ts +4 -0
  114. package/dist/types/storage/aws.d.ts.map +1 -0
  115. package/dist/types/storage/cloudflare.d.ts +34 -0
  116. package/dist/types/storage/cloudflare.d.ts.map +1 -0
  117. package/dist/types/storage/dynamo.d.ts +65 -0
  118. package/dist/types/storage/dynamo.d.ts.map +1 -0
  119. package/dist/types/storage/memory.d.ts +49 -0
  120. package/dist/types/storage/memory.d.ts.map +1 -0
  121. package/dist/types/storage/storage.d.ts +15 -0
  122. package/dist/types/storage/storage.d.ts.map +1 -0
  123. package/dist/types/subject.d.ts +122 -0
  124. package/dist/types/subject.d.ts.map +1 -0
  125. package/dist/types/ui/base.d.ts +5 -0
  126. package/dist/types/ui/base.d.ts.map +1 -0
  127. package/dist/types/ui/code.d.ts +104 -0
  128. package/dist/types/ui/code.d.ts.map +1 -0
  129. package/dist/types/ui/form.d.ts +6 -0
  130. package/dist/types/ui/form.d.ts.map +1 -0
  131. package/dist/types/ui/icon.d.ts +6 -0
  132. package/dist/types/ui/icon.d.ts.map +1 -0
  133. package/dist/types/ui/passkey.d.ts +5 -0
  134. package/dist/types/ui/passkey.d.ts.map +1 -0
  135. package/dist/types/ui/password.d.ts +139 -0
  136. package/dist/types/ui/password.d.ts.map +1 -0
  137. package/dist/types/ui/select.d.ts +55 -0
  138. package/dist/types/ui/select.d.ts.map +1 -0
  139. package/dist/types/ui/theme.d.ts +207 -0
  140. package/dist/types/ui/theme.d.ts.map +1 -0
  141. package/dist/types/util.d.ts +8 -0
  142. package/dist/types/util.d.ts.map +1 -0
  143. package/package.json +51 -0
  144. package/src/client.ts +749 -0
  145. package/src/css.d.ts +4 -0
  146. package/src/error.ts +120 -0
  147. package/src/index.ts +26 -0
  148. package/src/issuer.ts +1302 -0
  149. package/src/jwt.ts +17 -0
  150. package/src/keys.ts +139 -0
  151. package/src/pkce.ts +40 -0
  152. package/src/provider/apple.ts +127 -0
  153. package/src/provider/arctic.ts +66 -0
  154. package/src/provider/code.ts +227 -0
  155. package/src/provider/cognito.ts +74 -0
  156. package/src/provider/discord.ts +45 -0
  157. package/src/provider/facebook.ts +84 -0
  158. package/src/provider/github.ts +45 -0
  159. package/src/provider/google.ts +85 -0
  160. package/src/provider/index.ts +3 -0
  161. package/src/provider/jumpcloud.ts +45 -0
  162. package/src/provider/keycloak.ts +75 -0
  163. package/src/provider/linkedin.ts +12 -0
  164. package/src/provider/m2m.ts +56 -0
  165. package/src/provider/microsoft.ts +100 -0
  166. package/src/provider/oauth2.ts +297 -0
  167. package/src/provider/oidc.ts +179 -0
  168. package/src/provider/passkey.ts +655 -0
  169. package/src/provider/password.ts +672 -0
  170. package/src/provider/provider.ts +33 -0
  171. package/src/provider/slack.ts +67 -0
  172. package/src/provider/spotify.ts +45 -0
  173. package/src/provider/twitch.ts +45 -0
  174. package/src/provider/x.ts +46 -0
  175. package/src/provider/yahoo.ts +45 -0
  176. package/src/random.ts +24 -0
  177. package/src/storage/aws.ts +59 -0
  178. package/src/storage/cloudflare.ts +77 -0
  179. package/src/storage/dynamo.ts +193 -0
  180. package/src/storage/memory.ts +135 -0
  181. package/src/storage/storage.ts +46 -0
  182. package/src/subject.ts +130 -0
  183. package/src/ui/base.tsx +118 -0
  184. package/src/ui/code.tsx +215 -0
  185. package/src/ui/form.tsx +40 -0
  186. package/src/ui/icon.tsx +95 -0
  187. package/src/ui/passkey.tsx +321 -0
  188. package/src/ui/password.tsx +405 -0
  189. package/src/ui/select.tsx +221 -0
  190. package/src/ui/theme.ts +319 -0
  191. package/src/ui/ui.css +252 -0
  192. package/src/util.ts +58 -0
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Use this provider to authenticate with Slack.
3
+ *
4
+ * ```ts {5-10}
5
+ * import { SlackProvider } from "@openauthjs/openauth/provider/slack"
6
+ *
7
+ * export default issuer({
8
+ * providers: {
9
+ * slack: SlackProvider({
10
+ * team: "T1234567890",
11
+ * clientID: "1234567890",
12
+ * clientSecret: "0987654321",
13
+ * scopes: ["openid", "email", "profile"]
14
+ * })
15
+ * }
16
+ * })
17
+ * ```
18
+ *
19
+ * @packageDocumentation
20
+ */
21
+
22
+ import { Oauth2Provider, Oauth2WrappedConfig } from "./oauth2.js"
23
+
24
+ export interface SlackConfig extends Oauth2WrappedConfig {
25
+ /**
26
+ * The workspace the user is intending to authenticate.
27
+ *
28
+ * If that workspace has been previously authenticated, the user will be signed in directly,
29
+ * bypassing the consent screen.
30
+ */
31
+ team: string
32
+ /**
33
+ * The scopes to request from the user.
34
+ *
35
+ * | Scope | Description |
36
+ * |-|-|
37
+ * | `email` | Grants permission to access the user's email address. |
38
+ * | `profile` | Grants permission to access the user's profile information. |
39
+ * | `openid` | Grants permission to use OpenID Connect to verify the user's identity. |
40
+ */
41
+ scopes: ("email" | "profile" | "openid")[]
42
+ }
43
+
44
+ /**
45
+ * Creates a [Slack OAuth2 provider](https://api.slack.com/authentication/sign-in-with-slack).
46
+ *
47
+ * @param {SlackConfig} config - The config for the provider.
48
+ * @example
49
+ * ```ts
50
+ * SlackProvider({
51
+ * team: "T1234567890",
52
+ * clientID: "1234567890",
53
+ * clientSecret: "0987654321",
54
+ * scopes: ["openid", "email", "profile"]
55
+ * })
56
+ * ```
57
+ */
58
+ export function SlackProvider(config: SlackConfig) {
59
+ return Oauth2Provider({
60
+ ...config,
61
+ type: "slack",
62
+ endpoint: {
63
+ authorization: "https://slack.com/openid/connect/authorize",
64
+ token: "https://slack.com/api/openid.connect.token",
65
+ },
66
+ })
67
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Use this provider to authenticate with Spotify.
3
+ *
4
+ * ```ts {5-8}
5
+ * import { SpotifyProvider } from "@openauthjs/openauth/provider/spotify"
6
+ *
7
+ * export default issuer({
8
+ * providers: {
9
+ * spotify: SpotifyProvider({
10
+ * clientID: "1234567890",
11
+ * clientSecret: "0987654321"
12
+ * })
13
+ * }
14
+ * })
15
+ * ```
16
+ *
17
+ * @packageDocumentation
18
+ */
19
+
20
+ import { Oauth2Provider, type Oauth2WrappedConfig } from "./oauth2.js"
21
+
22
+ export interface SpotifyConfig extends Oauth2WrappedConfig {}
23
+
24
+ /**
25
+ * Create a Spotify OAuth2 provider.
26
+ *
27
+ * @param config - The config for the provider.
28
+ * @example
29
+ * ```ts
30
+ * SpotifyProvider({
31
+ * clientID: "1234567890",
32
+ * clientSecret: "0987654321"
33
+ * })
34
+ * ```
35
+ */
36
+ export function SpotifyProvider(config: SpotifyConfig) {
37
+ return Oauth2Provider({
38
+ ...config,
39
+ type: "spotify",
40
+ endpoint: {
41
+ authorization: "https://accounts.spotify.com/authorize",
42
+ token: "https://accounts.spotify.com/api/token",
43
+ },
44
+ })
45
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Use this provider to authenticate with Twitch.
3
+ *
4
+ * ```ts {5-8}
5
+ * import { TwitchProvider } from "@openauthjs/openauth/provider/twitch"
6
+ *
7
+ * export default issuer({
8
+ * providers: {
9
+ * twitch: TwitchProvider({
10
+ * clientID: "1234567890",
11
+ * clientSecret: "0987654321"
12
+ * })
13
+ * }
14
+ * })
15
+ * ```
16
+ *
17
+ * @packageDocumentation
18
+ */
19
+
20
+ import { Oauth2Provider, Oauth2WrappedConfig } from "./oauth2.js"
21
+
22
+ export interface TwitchConfig extends Oauth2WrappedConfig {}
23
+
24
+ /**
25
+ * Create a Twitch OAuth2 provider.
26
+ *
27
+ * @param config - The config for the provider.
28
+ * @example
29
+ * ```ts
30
+ * TwitchProvider({
31
+ * clientID: "1234567890",
32
+ * clientSecret: "0987654321"
33
+ * })
34
+ * ```
35
+ */
36
+ export function TwitchProvider(config: TwitchConfig) {
37
+ return Oauth2Provider({
38
+ type: "twitch",
39
+ ...config,
40
+ endpoint: {
41
+ authorization: "https://id.twitch.tv/oauth2/authorize",
42
+ token: "https://id.twitch.tv/oauth2/token",
43
+ },
44
+ })
45
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Use this provider to authenticate with X.com.
3
+ *
4
+ * ```ts {5-8}
5
+ * import { XProvider } from "@openauthjs/openauth/provider/x"
6
+ *
7
+ * export default issuer({
8
+ * providers: {
9
+ * x: XProvider({
10
+ * clientID: "1234567890",
11
+ * clientSecret: "0987654321"
12
+ * })
13
+ * }
14
+ * })
15
+ * ```
16
+ *
17
+ * @packageDocumentation
18
+ */
19
+
20
+ import { Oauth2Provider, Oauth2WrappedConfig } from "./oauth2.js"
21
+
22
+ export interface XProviderConfig extends Oauth2WrappedConfig {}
23
+
24
+ /**
25
+ * Create a X.com OAuth2 provider.
26
+ *
27
+ * @param config - The config for the provider.
28
+ * @example
29
+ * ```ts
30
+ * XProvider({
31
+ * clientID: "1234567890",
32
+ * clientSecret: "0987654321"
33
+ * })
34
+ * ```
35
+ */
36
+ export function XProvider(config: XProviderConfig) {
37
+ return Oauth2Provider({
38
+ ...config,
39
+ type: "x",
40
+ endpoint: {
41
+ authorization: "https://twitter.com/i/oauth2/authorize",
42
+ token: "https://api.x.com/2/oauth2/token",
43
+ },
44
+ pkce: true,
45
+ })
46
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Use this provider to authenticate with Yahoo.
3
+ *
4
+ * ```ts {5-8}
5
+ * import { YahooProvider } from "@openauthjs/openauth/provider/yahoo"
6
+ *
7
+ * export default issuer({
8
+ * providers: {
9
+ * yahoo: YahooProvider({
10
+ * clientID: "1234567890",
11
+ * clientSecret: "0987654321"
12
+ * })
13
+ * }
14
+ * })
15
+ * ```
16
+ *
17
+ * @packageDocumentation
18
+ */
19
+
20
+ import { Oauth2Provider, Oauth2WrappedConfig } from "./oauth2.js"
21
+
22
+ export interface YahooConfig extends Oauth2WrappedConfig {}
23
+
24
+ /**
25
+ * Create a Yahoo OAuth2 provider.
26
+ *
27
+ * @param config - The config for the provider.
28
+ * @example
29
+ * ```ts
30
+ * YahooProvider({
31
+ * clientID: "1234567890",
32
+ * clientSecret: "0987654321"
33
+ * })
34
+ * ```
35
+ */
36
+ export function YahooProvider(config: YahooConfig) {
37
+ return Oauth2Provider({
38
+ ...config,
39
+ type: "yahoo",
40
+ endpoint: {
41
+ authorization: "https://api.login.yahoo.com/oauth2/request_auth",
42
+ token: "https://api.login.yahoo.com/oauth2/get_token",
43
+ },
44
+ })
45
+ }
package/src/random.ts ADDED
@@ -0,0 +1,24 @@
1
+ import { timingSafeEqual } from "node:crypto"
2
+
3
+ export function generateUnbiasedDigits(length: number): string {
4
+ const result: number[] = []
5
+ while (result.length < length) {
6
+ const buffer = crypto.getRandomValues(new Uint8Array(length * 2))
7
+ for (const byte of buffer) {
8
+ if (byte < 250 && result.length < length) {
9
+ result.push(byte % 10)
10
+ }
11
+ }
12
+ }
13
+ return result.join("")
14
+ }
15
+
16
+ export function timingSafeCompare(a: string, b: string): boolean {
17
+ if (typeof a !== "string" || typeof b !== "string") {
18
+ return false
19
+ }
20
+ if (a.length !== b.length) {
21
+ return false
22
+ }
23
+ return timingSafeEqual(Buffer.from(a), Buffer.from(b))
24
+ }
@@ -0,0 +1,59 @@
1
+ import { AwsClient } from "aws4fetch"
2
+
3
+ interface EC2Credentials {
4
+ AccessKeyId: string
5
+ SecretAccessKey: string
6
+ Token: string
7
+ Expiration: string
8
+ Type: string
9
+ }
10
+
11
+ let cachedCredentials: EC2Credentials | null = null
12
+
13
+ async function getCredentials(url: string): Promise<EC2Credentials> {
14
+ if (cachedCredentials) {
15
+ const currentTime = new Date()
16
+ const fiveMinutesFromNow = new Date(currentTime.getTime() + 5 * 60000)
17
+ const expirationTime = new Date(cachedCredentials.Expiration)
18
+ if (expirationTime > fiveMinutesFromNow) {
19
+ return cachedCredentials
20
+ }
21
+ }
22
+
23
+ const credentials = (await fetch(url).then((res) =>
24
+ res.json(),
25
+ )) as EC2Credentials
26
+ cachedCredentials = credentials
27
+ return credentials
28
+ }
29
+
30
+ export async function client(): Promise<AwsClient> {
31
+ if (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY) {
32
+ return new AwsClient({
33
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
34
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
35
+ sessionToken: process.env.AWS_SESSION_TOKEN,
36
+ region: process.env.AWS_REGION,
37
+ })
38
+ }
39
+
40
+ if (process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) {
41
+ const credentials = await getCredentials(
42
+ "http://169.254.170.2" +
43
+ process.env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI,
44
+ )
45
+ return new AwsClient({
46
+ accessKeyId: credentials.AccessKeyId,
47
+ secretAccessKey: credentials.SecretAccessKey,
48
+ sessionToken: credentials.Token,
49
+ region: process.env.AWS_REGION,
50
+ })
51
+ }
52
+
53
+ throw new Error("No AWS credentials found")
54
+ }
55
+
56
+ export type AwsOptions = Exclude<
57
+ Parameters<AwsClient["fetch"]>[1],
58
+ null | undefined
59
+ >["aws"]
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Configure OpenAuth to use [Cloudflare KV](https://developers.cloudflare.com/kv/) as a
3
+ * storage adapter.
4
+ *
5
+ * ```ts
6
+ * import { CloudflareStorage } from "@openauthjs/openauth/storage/cloudflare"
7
+ *
8
+ * const storage = CloudflareStorage({
9
+ * namespace: "my-namespace"
10
+ * })
11
+ *
12
+ *
13
+ * export default issuer({
14
+ * storage,
15
+ * // ...
16
+ * })
17
+ * ```
18
+ *
19
+ * @packageDocumentation
20
+ */
21
+ import type { KVNamespace } from "@cloudflare/workers-types"
22
+ import { joinKey, splitKey, StorageAdapter } from "./storage.js"
23
+
24
+ /**
25
+ * Configure the Cloudflare KV store that's created.
26
+ */
27
+ export interface CloudflareStorageOptions {
28
+ namespace: KVNamespace
29
+ }
30
+ /**
31
+ * Creates a Cloudflare KV store.
32
+ * @param options - The config for the adapter.
33
+ */
34
+ export function CloudflareStorage(
35
+ options: CloudflareStorageOptions,
36
+ ): StorageAdapter {
37
+ return {
38
+ async get(key: string[]) {
39
+ const value = await options.namespace.get(joinKey(key), "json")
40
+ if (!value) return
41
+ return value as Record<string, any>
42
+ },
43
+
44
+ async set(key: string[], value: any, expiry?: Date) {
45
+ await options.namespace.put(joinKey(key), JSON.stringify(value), {
46
+ expirationTtl: expiry
47
+ ? Math.max(Math.floor((expiry.getTime() - Date.now()) / 1000), 60)
48
+ : undefined,
49
+ })
50
+ },
51
+
52
+ async remove(key: string[]) {
53
+ await options.namespace.delete(joinKey(key))
54
+ },
55
+
56
+ async *scan(prefix: string[]) {
57
+ let cursor: string | undefined
58
+ while (true) {
59
+ const result = await options.namespace.list({
60
+ prefix: joinKey([...prefix, ""]),
61
+ cursor,
62
+ })
63
+
64
+ for (const key of result.keys) {
65
+ const value = await options.namespace.get(key.name, "json")
66
+ if (value !== null) {
67
+ yield [splitKey(key.name), value]
68
+ }
69
+ }
70
+ if (result.list_complete) {
71
+ break
72
+ }
73
+ cursor = result.cursor
74
+ }
75
+ },
76
+ }
77
+ }
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Configure OpenAuth to use [DynamoDB](https://aws.amazon.com/dynamodb/) as a storage adapter.
3
+ *
4
+ * ```ts
5
+ * import { DynamoStorage } from "@openauthjs/openauth/storage/dynamo"
6
+ *
7
+ * const storage = DynamoStorage({
8
+ * table: "my-table",
9
+ * pk: "pk",
10
+ * sk: "sk"
11
+ * })
12
+ *
13
+ * export default issuer({
14
+ * storage,
15
+ * // ...
16
+ * })
17
+ * ```
18
+ *
19
+ * @packageDocumentation
20
+ */
21
+
22
+ import { client } from "./aws.js"
23
+ import { joinKey, StorageAdapter } from "./storage.js"
24
+
25
+ /**
26
+ * Configure the DynamoDB table that's created.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * {
31
+ * table: "my-table",
32
+ * pk: "pk",
33
+ * sk: "sk"
34
+ * }
35
+ * ```
36
+ */
37
+ export interface DynamoStorageOptions {
38
+ /**
39
+ * The name of the DynamoDB table.
40
+ */
41
+ table: string
42
+ /**
43
+ * The primary key column name.
44
+ * @default "pk"
45
+ */
46
+ pk?: string
47
+ /**
48
+ * The sort key column name.
49
+ * @default "sk"
50
+ */
51
+ sk?: string
52
+ /**
53
+ * Endpoint URL for the DynamoDB service. Useful for local testing.
54
+ * @default "https://dynamodb.{region}.amazonaws.com"
55
+ */
56
+ endpoint?: string
57
+ /**
58
+ * The name of the time to live attribute.
59
+ * @default "expiry"
60
+ */
61
+ ttl?: string
62
+ }
63
+
64
+ /**
65
+ * Creates a DynamoDB store.
66
+ * @param options - The config for the adapter.
67
+ */
68
+ export function DynamoStorage(options: DynamoStorageOptions): StorageAdapter {
69
+ const pk = options.pk || "pk"
70
+ const sk = options.sk || "sk"
71
+ const ttl = options.ttl || "expiry"
72
+ const tableName = options.table
73
+
74
+ function parseKey(key: string[]) {
75
+ if (key.length === 2) {
76
+ return {
77
+ pk: key[0],
78
+ sk: key[1],
79
+ }
80
+ }
81
+ return {
82
+ pk: joinKey(key.slice(0, 2)),
83
+ sk: joinKey(key.slice(2)),
84
+ }
85
+ }
86
+
87
+ async function dynamo(action: string, payload: any) {
88
+ const c = await client()
89
+ const endpoint =
90
+ options.endpoint || `https://dynamodb.${c.region}.amazonaws.com`
91
+ const response = await c.fetch(endpoint, {
92
+ method: "POST",
93
+ headers: {
94
+ "Content-Type": "application/x-amz-json-1.0",
95
+ "X-Amz-Target": `DynamoDB_20120810.${action}`,
96
+ },
97
+ body: JSON.stringify(payload),
98
+ })
99
+
100
+ if (!response.ok) {
101
+ throw new Error(`DynamoDB request failed: ${response.statusText}`)
102
+ }
103
+
104
+ return response.json() as Promise<any>
105
+ }
106
+
107
+ return {
108
+ async get(key: string[]) {
109
+ const { pk: keyPk, sk: keySk } = parseKey(key)
110
+ const params = {
111
+ TableName: tableName,
112
+ Key: {
113
+ [pk]: { S: keyPk },
114
+ [sk]: { S: keySk },
115
+ },
116
+ }
117
+ const result = await dynamo("GetItem", params)
118
+ if (!result.Item) return
119
+ if (result.Item[ttl] && result.Item[ttl].N < Date.now() / 1000) {
120
+ return
121
+ }
122
+ return JSON.parse(result.Item.value.S)
123
+ },
124
+
125
+ async set(key: string[], value: any, expiry?: Date) {
126
+ const parsed = parseKey(key)
127
+ const params = {
128
+ TableName: tableName,
129
+ Item: {
130
+ [pk]: { S: parsed.pk },
131
+ [sk]: { S: parsed.sk },
132
+ ...(expiry
133
+ ? {
134
+ [ttl]: { N: Math.floor(expiry.getTime() / 1000).toString() },
135
+ }
136
+ : {}),
137
+ value: { S: JSON.stringify(value) },
138
+ },
139
+ }
140
+ await dynamo("PutItem", params)
141
+ },
142
+
143
+ async remove(key: string[]) {
144
+ const { pk: keyPk, sk: keySk } = parseKey(key)
145
+ const params = {
146
+ TableName: tableName,
147
+ Key: {
148
+ [pk]: { S: keyPk },
149
+ [sk]: { S: keySk },
150
+ },
151
+ }
152
+
153
+ await dynamo("DeleteItem", params)
154
+ },
155
+
156
+ async *scan(prefix: string[]) {
157
+ const prefixPk =
158
+ prefix.length >= 2 ? joinKey(prefix.slice(0, 2)) : prefix[0]
159
+ const prefixSk = prefix.length > 2 ? joinKey(prefix.slice(2)) : ""
160
+ let lastEvaluatedKey = undefined
161
+ const now = Date.now() / 1000
162
+ while (true) {
163
+ const params = {
164
+ TableName: tableName,
165
+ ExclusiveStartKey: lastEvaluatedKey,
166
+ KeyConditionExpression: prefixSk
167
+ ? `#pk = :pk AND begins_with(#sk, :sk)`
168
+ : `#pk = :pk`,
169
+ ExpressionAttributeNames: {
170
+ "#pk": pk,
171
+ ...(prefixSk && { "#sk": sk }),
172
+ },
173
+ ExpressionAttributeValues: {
174
+ ":pk": { S: prefixPk },
175
+ ...(prefixSk && { ":sk": { S: prefixSk } }),
176
+ },
177
+ }
178
+
179
+ const result = await dynamo("Query", params)
180
+
181
+ for (const item of result.Items || []) {
182
+ if (item[ttl] && item[ttl].N < now) {
183
+ continue
184
+ }
185
+ yield [[item[pk].S, item[sk].S], JSON.parse(item.value.S)]
186
+ }
187
+
188
+ if (!result.LastEvaluatedKey) break
189
+ lastEvaluatedKey = result.LastEvaluatedKey
190
+ }
191
+ },
192
+ }
193
+ }