@_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.
- package/dist/esm/client.js +186 -0
- package/dist/esm/css.d.js +0 -0
- package/dist/esm/error.js +73 -0
- package/dist/esm/index.js +14 -0
- package/dist/esm/issuer.js +558 -0
- package/dist/esm/jwt.js +16 -0
- package/dist/esm/keys.js +113 -0
- package/dist/esm/pkce.js +35 -0
- package/dist/esm/provider/apple.js +28 -0
- package/dist/esm/provider/arctic.js +43 -0
- package/dist/esm/provider/code.js +58 -0
- package/dist/esm/provider/cognito.js +16 -0
- package/dist/esm/provider/discord.js +15 -0
- package/dist/esm/provider/facebook.js +24 -0
- package/dist/esm/provider/github.js +15 -0
- package/dist/esm/provider/google.js +25 -0
- package/dist/esm/provider/index.js +3 -0
- package/dist/esm/provider/jumpcloud.js +15 -0
- package/dist/esm/provider/keycloak.js +15 -0
- package/dist/esm/provider/linkedin.js +15 -0
- package/dist/esm/provider/m2m.js +17 -0
- package/dist/esm/provider/microsoft.js +24 -0
- package/dist/esm/provider/oauth2.js +119 -0
- package/dist/esm/provider/oidc.js +69 -0
- package/dist/esm/provider/passkey.js +315 -0
- package/dist/esm/provider/password.js +306 -0
- package/dist/esm/provider/provider.js +10 -0
- package/dist/esm/provider/slack.js +15 -0
- package/dist/esm/provider/spotify.js +15 -0
- package/dist/esm/provider/twitch.js +15 -0
- package/dist/esm/provider/x.js +16 -0
- package/dist/esm/provider/yahoo.js +15 -0
- package/dist/esm/random.js +27 -0
- package/dist/esm/storage/aws.js +39 -0
- package/dist/esm/storage/cloudflare.js +42 -0
- package/dist/esm/storage/dynamo.js +116 -0
- package/dist/esm/storage/memory.js +88 -0
- package/dist/esm/storage/storage.js +36 -0
- package/dist/esm/subject.js +7 -0
- package/dist/esm/ui/base.js +407 -0
- package/dist/esm/ui/code.js +151 -0
- package/dist/esm/ui/form.js +43 -0
- package/dist/esm/ui/icon.js +92 -0
- package/dist/esm/ui/passkey.js +329 -0
- package/dist/esm/ui/password.js +338 -0
- package/dist/esm/ui/select.js +187 -0
- package/dist/esm/ui/theme.js +115 -0
- package/dist/esm/util.js +54 -0
- package/dist/types/client.d.ts +466 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/error.d.ts +77 -0
- package/dist/types/error.d.ts.map +1 -0
- package/dist/types/index.d.ts +20 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/issuer.d.ts +465 -0
- package/dist/types/issuer.d.ts.map +1 -0
- package/dist/types/jwt.d.ts +6 -0
- package/dist/types/jwt.d.ts.map +1 -0
- package/dist/types/keys.d.ts +18 -0
- package/dist/types/keys.d.ts.map +1 -0
- package/dist/types/pkce.d.ts +7 -0
- package/dist/types/pkce.d.ts.map +1 -0
- package/dist/types/provider/apple.d.ts +108 -0
- package/dist/types/provider/apple.d.ts.map +1 -0
- package/dist/types/provider/arctic.d.ts +16 -0
- package/dist/types/provider/arctic.d.ts.map +1 -0
- package/dist/types/provider/code.d.ts +74 -0
- package/dist/types/provider/code.d.ts.map +1 -0
- package/dist/types/provider/cognito.d.ts +64 -0
- package/dist/types/provider/cognito.d.ts.map +1 -0
- package/dist/types/provider/discord.d.ts +38 -0
- package/dist/types/provider/discord.d.ts.map +1 -0
- package/dist/types/provider/facebook.d.ts +74 -0
- package/dist/types/provider/facebook.d.ts.map +1 -0
- package/dist/types/provider/github.d.ts +38 -0
- package/dist/types/provider/github.d.ts.map +1 -0
- package/dist/types/provider/google.d.ts +74 -0
- package/dist/types/provider/google.d.ts.map +1 -0
- package/dist/types/provider/index.d.ts +4 -0
- package/dist/types/provider/index.d.ts.map +1 -0
- package/dist/types/provider/jumpcloud.d.ts +38 -0
- package/dist/types/provider/jumpcloud.d.ts.map +1 -0
- package/dist/types/provider/keycloak.d.ts +67 -0
- package/dist/types/provider/keycloak.d.ts.map +1 -0
- package/dist/types/provider/linkedin.d.ts +6 -0
- package/dist/types/provider/linkedin.d.ts.map +1 -0
- package/dist/types/provider/m2m.d.ts +34 -0
- package/dist/types/provider/m2m.d.ts.map +1 -0
- package/dist/types/provider/microsoft.d.ts +89 -0
- package/dist/types/provider/microsoft.d.ts.map +1 -0
- package/dist/types/provider/oauth2.d.ts +133 -0
- package/dist/types/provider/oauth2.d.ts.map +1 -0
- package/dist/types/provider/oidc.d.ts +91 -0
- package/dist/types/provider/oidc.d.ts.map +1 -0
- package/dist/types/provider/passkey.d.ts +143 -0
- package/dist/types/provider/passkey.d.ts.map +1 -0
- package/dist/types/provider/password.d.ts +210 -0
- package/dist/types/provider/password.d.ts.map +1 -0
- package/dist/types/provider/provider.d.ts +29 -0
- package/dist/types/provider/provider.d.ts.map +1 -0
- package/dist/types/provider/slack.d.ts +59 -0
- package/dist/types/provider/slack.d.ts.map +1 -0
- package/dist/types/provider/spotify.d.ts +38 -0
- package/dist/types/provider/spotify.d.ts.map +1 -0
- package/dist/types/provider/twitch.d.ts +38 -0
- package/dist/types/provider/twitch.d.ts.map +1 -0
- package/dist/types/provider/x.d.ts +38 -0
- package/dist/types/provider/x.d.ts.map +1 -0
- package/dist/types/provider/yahoo.d.ts +38 -0
- package/dist/types/provider/yahoo.d.ts.map +1 -0
- package/dist/types/random.d.ts +3 -0
- package/dist/types/random.d.ts.map +1 -0
- package/dist/types/storage/aws.d.ts +4 -0
- package/dist/types/storage/aws.d.ts.map +1 -0
- package/dist/types/storage/cloudflare.d.ts +34 -0
- package/dist/types/storage/cloudflare.d.ts.map +1 -0
- package/dist/types/storage/dynamo.d.ts +65 -0
- package/dist/types/storage/dynamo.d.ts.map +1 -0
- package/dist/types/storage/memory.d.ts +49 -0
- package/dist/types/storage/memory.d.ts.map +1 -0
- package/dist/types/storage/storage.d.ts +15 -0
- package/dist/types/storage/storage.d.ts.map +1 -0
- package/dist/types/subject.d.ts +122 -0
- package/dist/types/subject.d.ts.map +1 -0
- package/dist/types/ui/base.d.ts +5 -0
- package/dist/types/ui/base.d.ts.map +1 -0
- package/dist/types/ui/code.d.ts +104 -0
- package/dist/types/ui/code.d.ts.map +1 -0
- package/dist/types/ui/form.d.ts +6 -0
- package/dist/types/ui/form.d.ts.map +1 -0
- package/dist/types/ui/icon.d.ts +6 -0
- package/dist/types/ui/icon.d.ts.map +1 -0
- package/dist/types/ui/passkey.d.ts +5 -0
- package/dist/types/ui/passkey.d.ts.map +1 -0
- package/dist/types/ui/password.d.ts +139 -0
- package/dist/types/ui/password.d.ts.map +1 -0
- package/dist/types/ui/select.d.ts +55 -0
- package/dist/types/ui/select.d.ts.map +1 -0
- package/dist/types/ui/theme.d.ts +207 -0
- package/dist/types/ui/theme.d.ts.map +1 -0
- package/dist/types/util.d.ts +8 -0
- package/dist/types/util.d.ts.map +1 -0
- package/package.json +51 -0
- package/src/client.ts +749 -0
- package/src/css.d.ts +4 -0
- package/src/error.ts +120 -0
- package/src/index.ts +26 -0
- package/src/issuer.ts +1302 -0
- package/src/jwt.ts +17 -0
- package/src/keys.ts +139 -0
- package/src/pkce.ts +40 -0
- package/src/provider/apple.ts +127 -0
- package/src/provider/arctic.ts +66 -0
- package/src/provider/code.ts +227 -0
- package/src/provider/cognito.ts +74 -0
- package/src/provider/discord.ts +45 -0
- package/src/provider/facebook.ts +84 -0
- package/src/provider/github.ts +45 -0
- package/src/provider/google.ts +85 -0
- package/src/provider/index.ts +3 -0
- package/src/provider/jumpcloud.ts +45 -0
- package/src/provider/keycloak.ts +75 -0
- package/src/provider/linkedin.ts +12 -0
- package/src/provider/m2m.ts +56 -0
- package/src/provider/microsoft.ts +100 -0
- package/src/provider/oauth2.ts +297 -0
- package/src/provider/oidc.ts +179 -0
- package/src/provider/passkey.ts +655 -0
- package/src/provider/password.ts +672 -0
- package/src/provider/provider.ts +33 -0
- package/src/provider/slack.ts +67 -0
- package/src/provider/spotify.ts +45 -0
- package/src/provider/twitch.ts +45 -0
- package/src/provider/x.ts +46 -0
- package/src/provider/yahoo.ts +45 -0
- package/src/random.ts +24 -0
- package/src/storage/aws.ts +59 -0
- package/src/storage/cloudflare.ts +77 -0
- package/src/storage/dynamo.ts +193 -0
- package/src/storage/memory.ts +135 -0
- package/src/storage/storage.ts +46 -0
- package/src/subject.ts +130 -0
- package/src/ui/base.tsx +118 -0
- package/src/ui/code.tsx +215 -0
- package/src/ui/form.tsx +40 -0
- package/src/ui/icon.tsx +95 -0
- package/src/ui/passkey.tsx +321 -0
- package/src/ui/password.tsx +405 -0
- package/src/ui/select.tsx +221 -0
- package/src/ui/theme.ts +319 -0
- package/src/ui/ui.css +252 -0
- 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
|
+
}
|