nuxt-auth-utils 0.5.11 → 0.5.12
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/README.md +31 -8
- package/dist/module.d.mts +5 -0
- package/dist/module.d.ts +5 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +47 -0
- package/dist/runtime/server/api/session.get.d.ts +1 -1
- package/dist/runtime/server/lib/atproto/bluesky.d.ts +39 -0
- package/dist/runtime/server/lib/atproto/bluesky.js +101 -0
- package/dist/runtime/server/routes/atproto/client-metadata.json.get.d.ts +129 -0
- package/dist/runtime/server/routes/atproto/client-metadata.json.get.js +18 -0
- package/dist/runtime/server/utils/atproto.d.ts +5 -0
- package/dist/runtime/server/utils/atproto.js +35 -0
- package/dist/runtime/server/utils/session.d.ts +5 -1
- package/dist/runtime/server/utils/session.js +5 -1
- package/dist/runtime/types/atproto.d.ts +62 -0
- package/dist/runtime/types/atproto.js +0 -0
- package/dist/runtime/types/index.d.ts +1 -1
- package/dist/runtime/types/oauth-config.d.ts +2 -1
- package/dist/runtime/types/session.d.ts +4 -0
- package/package.json +17 -9
package/README.md
CHANGED
|
@@ -217,6 +217,7 @@ It can also be set using environment variables:
|
|
|
217
217
|
- Authentik
|
|
218
218
|
- AWS Cognito
|
|
219
219
|
- Battle.net
|
|
220
|
+
- Bluesky (AT Protocol)
|
|
220
221
|
- Discord
|
|
221
222
|
- Dropbox
|
|
222
223
|
- Facebook
|
|
@@ -304,6 +305,28 @@ export default defineNuxtConfig({
|
|
|
304
305
|
})
|
|
305
306
|
```
|
|
306
307
|
|
|
308
|
+
### AT Protocol
|
|
309
|
+
|
|
310
|
+
Social networks that rely on AT Protocol (e.g., Bluesky) slightly differ from a regular OAuth flow.
|
|
311
|
+
|
|
312
|
+
To enable OAuth with AT Protocol, you need to:
|
|
313
|
+
|
|
314
|
+
1. Install the peer dependencies:
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
npx nypm i @atproto/oauth-client-node @atproto/api
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
2. Enable it in your `nuxt.config.ts`
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
export default defineNuxtConfig({
|
|
324
|
+
auth: {
|
|
325
|
+
atproto: true
|
|
326
|
+
}
|
|
327
|
+
})
|
|
328
|
+
```
|
|
329
|
+
|
|
307
330
|
### WebAuthn (passkey)
|
|
308
331
|
|
|
309
332
|
WebAuthn (Web Authentication) is a web standard that enhances security by replacing passwords with passkeys using public key cryptography. Users can authenticate with biometric data (like fingerprints or facial recognition) or physical devices (like USB keys), reducing the risk of phishing and password breaches. This approach offers a more secure and user-friendly authentication method, supported by major browsers and platforms.
|
|
@@ -711,26 +734,26 @@ Checkout the [`SessionConfig`](https://github.com/unjs/h3/blob/c04c458810e34eb15
|
|
|
711
734
|
|
|
712
735
|
```bash
|
|
713
736
|
# Install dependencies
|
|
714
|
-
|
|
737
|
+
pnpm install
|
|
715
738
|
|
|
716
739
|
# Generate type stubs
|
|
717
|
-
|
|
740
|
+
pnpm run dev:prepare
|
|
718
741
|
|
|
719
742
|
# Develop with the playground
|
|
720
|
-
|
|
743
|
+
pnpm run dev
|
|
721
744
|
|
|
722
745
|
# Build the playground
|
|
723
|
-
|
|
746
|
+
pnpm run dev:build
|
|
724
747
|
|
|
725
748
|
# Run ESLint
|
|
726
|
-
|
|
749
|
+
pnpm run lint
|
|
727
750
|
|
|
728
751
|
# Run Vitest
|
|
729
|
-
|
|
730
|
-
|
|
752
|
+
pnpm run test
|
|
753
|
+
pnpm run test:watch
|
|
731
754
|
|
|
732
755
|
# Release new version
|
|
733
|
-
|
|
756
|
+
pnpm run release
|
|
734
757
|
```
|
|
735
758
|
|
|
736
759
|
<!-- Badges -->
|
package/dist/module.d.mts
CHANGED
package/dist/module.d.ts
CHANGED
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -4,6 +4,26 @@ import { join } from 'pathe';
|
|
|
4
4
|
import { defu } from 'defu';
|
|
5
5
|
import { randomUUID } from 'uncrypto';
|
|
6
6
|
|
|
7
|
+
const atprotoProviders = ["bluesky"];
|
|
8
|
+
const atprotoProviderDefaultClientMetadata = {
|
|
9
|
+
clientMetadataFilename: "",
|
|
10
|
+
clientName: "",
|
|
11
|
+
clientUri: "",
|
|
12
|
+
logoUri: "",
|
|
13
|
+
policyUri: "",
|
|
14
|
+
tosUri: "",
|
|
15
|
+
scope: ["atproto"],
|
|
16
|
+
grantTypes: ["authorization_code"],
|
|
17
|
+
responseTypes: ["code"],
|
|
18
|
+
applicationType: "web",
|
|
19
|
+
redirectUris: [],
|
|
20
|
+
dpopBoundAccessTokens: true,
|
|
21
|
+
tokenEndpointAuthMethod: "none"
|
|
22
|
+
};
|
|
23
|
+
function getClientMetadataFilename(provider, config) {
|
|
24
|
+
return config?.clientMetadataFilename || provider + "/client-metadata.json";
|
|
25
|
+
}
|
|
26
|
+
|
|
7
27
|
const module = defineNuxtModule({
|
|
8
28
|
meta: {
|
|
9
29
|
name: "auth-utils",
|
|
@@ -12,6 +32,7 @@ const module = defineNuxtModule({
|
|
|
12
32
|
// Default configuration options of the Nuxt module
|
|
13
33
|
defaults: {
|
|
14
34
|
webAuthn: false,
|
|
35
|
+
atproto: false,
|
|
15
36
|
hash: {
|
|
16
37
|
scrypt: {}
|
|
17
38
|
}
|
|
@@ -163,6 +184,32 @@ const module = defineNuxtModule({
|
|
|
163
184
|
clientSecret: "",
|
|
164
185
|
redirectURL: ""
|
|
165
186
|
});
|
|
187
|
+
for (const provider of atprotoProviders) {
|
|
188
|
+
runtimeConfig.oauth[provider] = defu(runtimeConfig.oauth[provider], atprotoProviderDefaultClientMetadata);
|
|
189
|
+
}
|
|
190
|
+
if (options.atproto) {
|
|
191
|
+
const missingDeps = [];
|
|
192
|
+
const peerDeps = ["@atproto/oauth-client-node", "@atproto/api"];
|
|
193
|
+
for (const pkg of peerDeps) {
|
|
194
|
+
await import(pkg).catch(() => {
|
|
195
|
+
missingDeps.push(pkg);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (missingDeps.length > 0) {
|
|
199
|
+
logger.withTag("nuxt-auth-utils").error(`Missing dependencies for \`atproto\`, please install with:
|
|
200
|
+
|
|
201
|
+
\`npx nypm i ${missingDeps.join(" ")}\``);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
for (const provider of atprotoProviders) {
|
|
205
|
+
addServerHandler({
|
|
206
|
+
handler: resolver.resolve("./runtime/server/routes/atproto/client-metadata.json.get.ts"),
|
|
207
|
+
route: "/" + getClientMetadataFilename(provider, runtimeConfig.oauth[provider]),
|
|
208
|
+
method: "get"
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
addServerImportsDir(resolver.resolve("./runtime/server/lib/atproto"));
|
|
212
|
+
}
|
|
166
213
|
runtimeConfig.oauth.keycloak = defu(runtimeConfig.oauth.keycloak, {
|
|
167
214
|
clientId: "",
|
|
168
215
|
clientSecret: "",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
2
|
+
import type { NodeSavedSession, NodeSavedSessionStore, NodeSavedState, NodeSavedStateStore } from '@atproto/oauth-client-node';
|
|
3
|
+
import type { AppBskyActorDefs } from '@atproto/api';
|
|
4
|
+
import type { OAuthConfig } from '#auth-utils';
|
|
5
|
+
export interface OAuthBlueskyConfig {
|
|
6
|
+
/**
|
|
7
|
+
* Redirect URL to use for this authorization flow. It should only consist of the path, as the hostname must always match the client id's hostname.
|
|
8
|
+
* @default process.env.NUXT_OAUTH_BLUESKY_REDIRECT_URL
|
|
9
|
+
* @example '/auth/bluesky'
|
|
10
|
+
*/
|
|
11
|
+
redirectUrl?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Bluesky OAuth Scope. The `atproto` scope is required and will be added if not present.
|
|
14
|
+
* @default ['atproto']
|
|
15
|
+
* @see https://atproto.com/specs/oauth#authorization-scopes
|
|
16
|
+
* @example ['atproto', 'transition:generic']
|
|
17
|
+
*/
|
|
18
|
+
scope?: string[];
|
|
19
|
+
}
|
|
20
|
+
type BlueSkyUser = AppBskyActorDefs.ProfileViewDetailed | Pick<AppBskyActorDefs.ProfileView, 'did'>;
|
|
21
|
+
type BlueSkyTokens = NodeSavedSession['tokenSet'];
|
|
22
|
+
export declare function defineOAuthBlueskyEventHandler({ config, onSuccess, onError }: OAuthConfig<OAuthBlueskyConfig, BlueSkyUser, BlueSkyTokens>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
23
|
+
export declare class StateStore implements NodeSavedStateStore {
|
|
24
|
+
private event;
|
|
25
|
+
private readonly stateKey;
|
|
26
|
+
constructor(event: H3Event);
|
|
27
|
+
get(): Promise<NodeSavedState | undefined>;
|
|
28
|
+
set(key: string, val: NodeSavedState): Promise<void>;
|
|
29
|
+
del(): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
export declare class SessionStore implements NodeSavedSessionStore {
|
|
32
|
+
private event;
|
|
33
|
+
private readonly sessionKey;
|
|
34
|
+
constructor(event: H3Event);
|
|
35
|
+
get(): Promise<NodeSavedSession | undefined>;
|
|
36
|
+
set(key: string, val: NodeSavedSession): Promise<void>;
|
|
37
|
+
del(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
export {};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { createError, eventHandler, getQuery, sendRedirect, getCookie, setCookie, deleteCookie } from "h3";
|
|
2
|
+
import { NodeOAuthClient, OAuthCallbackError, OAuthResolverError, OAuthResponseError } from "@atproto/oauth-client-node";
|
|
3
|
+
import { Agent } from "@atproto/api";
|
|
4
|
+
import { getAtprotoClientMetadata } from "../../utils/atproto.js";
|
|
5
|
+
export function defineOAuthBlueskyEventHandler({ config, onSuccess, onError }) {
|
|
6
|
+
return eventHandler(async (event) => {
|
|
7
|
+
const clientMetadata = getAtprotoClientMetadata(event, "bluesky", config);
|
|
8
|
+
const scopes = clientMetadata.scope?.split(" ") ?? [];
|
|
9
|
+
const sessionStore = new SessionStore(event);
|
|
10
|
+
const stateStore = new StateStore(event);
|
|
11
|
+
const client = new NodeOAuthClient({
|
|
12
|
+
stateStore,
|
|
13
|
+
sessionStore,
|
|
14
|
+
clientMetadata
|
|
15
|
+
});
|
|
16
|
+
const query = getQuery(event);
|
|
17
|
+
if (!query.code) {
|
|
18
|
+
try {
|
|
19
|
+
const handle = query.handle?.toString();
|
|
20
|
+
if (!handle) throw createError({
|
|
21
|
+
statusCode: 400,
|
|
22
|
+
message: "Query parameter `handle` empty or missing. Please provide a valid Bluesky handle."
|
|
23
|
+
});
|
|
24
|
+
const url = await client.authorize(handle, { scope: clientMetadata.scope });
|
|
25
|
+
return sendRedirect(event, url.toString());
|
|
26
|
+
} catch (err) {
|
|
27
|
+
const error = (() => {
|
|
28
|
+
switch (true) {
|
|
29
|
+
case err instanceof OAuthResponseError:
|
|
30
|
+
return createError({
|
|
31
|
+
statusCode: 500,
|
|
32
|
+
message: `Bluesky login failed: ${err.errorDescription || "Unknown error"}`,
|
|
33
|
+
data: err.payload
|
|
34
|
+
});
|
|
35
|
+
case err instanceof OAuthResolverError:
|
|
36
|
+
return createError({
|
|
37
|
+
statusCode: 400,
|
|
38
|
+
message: `Bluesky login failed: ${err.message || "Unknown error"}`
|
|
39
|
+
});
|
|
40
|
+
default:
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
})();
|
|
44
|
+
if (!onError) throw error;
|
|
45
|
+
return onError(event, error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const { session } = await client.callback(new URLSearchParams(query));
|
|
50
|
+
const sessionInfo = await sessionStore.get();
|
|
51
|
+
const profile = scopes.includes("transition:generic") ? (await new Agent(session).getProfile({ actor: session.did })).data : null;
|
|
52
|
+
sessionStore.del();
|
|
53
|
+
return onSuccess(event, {
|
|
54
|
+
user: profile ?? { did: session.did },
|
|
55
|
+
tokens: sessionInfo.tokenSet
|
|
56
|
+
});
|
|
57
|
+
} catch (err) {
|
|
58
|
+
if (!(err instanceof OAuthCallbackError)) throw err;
|
|
59
|
+
const error = createError({
|
|
60
|
+
statusCode: 500,
|
|
61
|
+
message: `Bluesky login failed: ${err.message || "Unknown error"}`
|
|
62
|
+
});
|
|
63
|
+
if (!onError) throw error;
|
|
64
|
+
return onError(event, error);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
export class StateStore {
|
|
69
|
+
constructor(event) {
|
|
70
|
+
this.event = event;
|
|
71
|
+
}
|
|
72
|
+
stateKey = "oauth:bluesky:stat";
|
|
73
|
+
async get() {
|
|
74
|
+
const result = getCookie(this.event, this.stateKey);
|
|
75
|
+
if (!result) return;
|
|
76
|
+
return JSON.parse(atob(result));
|
|
77
|
+
}
|
|
78
|
+
async set(key, val) {
|
|
79
|
+
setCookie(this.event, this.stateKey, btoa(JSON.stringify(val)));
|
|
80
|
+
}
|
|
81
|
+
async del() {
|
|
82
|
+
deleteCookie(this.event, this.stateKey);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export class SessionStore {
|
|
86
|
+
constructor(event) {
|
|
87
|
+
this.event = event;
|
|
88
|
+
}
|
|
89
|
+
sessionKey = "oauth:bluesky:session";
|
|
90
|
+
async get() {
|
|
91
|
+
const result = getCookie(this.event, this.sessionKey);
|
|
92
|
+
if (!result) return;
|
|
93
|
+
return JSON.parse(atob(result));
|
|
94
|
+
}
|
|
95
|
+
async set(key, val) {
|
|
96
|
+
setCookie(this.event, this.sessionKey, btoa(JSON.stringify(val)));
|
|
97
|
+
}
|
|
98
|
+
async del() {
|
|
99
|
+
deleteCookie(this.event, this.sessionKey);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, {
|
|
2
|
+
redirect_uris: [string, ...string[]];
|
|
3
|
+
scope?: string | undefined;
|
|
4
|
+
response_types?: ["code" | "none" | "token" | "code id_token token" | "code id_token" | "code token" | "id_token token" | "id_token", ...("code" | "none" | "token" | "code id_token token" | "code id_token" | "code token" | "id_token token" | "id_token")[]] | undefined;
|
|
5
|
+
grant_types?: ["authorization_code" | "implicit" | "refresh_token" | "password" | "client_credentials" | "urn:ietf:params:oauth:grant-type:jwt-bearer" | "urn:ietf:params:oauth:grant-type:saml2-bearer", ...("authorization_code" | "implicit" | "refresh_token" | "password" | "client_credentials" | "urn:ietf:params:oauth:grant-type:jwt-bearer" | "urn:ietf:params:oauth:grant-type:saml2-bearer")[]] | undefined;
|
|
6
|
+
token_endpoint_auth_method?: "client_secret_basic" | "client_secret_jwt" | "client_secret_post" | "none" | "private_key_jwt" | "self_signed_tls_client_auth" | "tls_client_auth" | undefined;
|
|
7
|
+
token_endpoint_auth_signing_alg?: string | undefined;
|
|
8
|
+
userinfo_signed_response_alg?: string | undefined;
|
|
9
|
+
userinfo_encrypted_response_alg?: string | undefined;
|
|
10
|
+
jwks_uri?: string | undefined;
|
|
11
|
+
jwks?: {
|
|
12
|
+
keys: ({
|
|
13
|
+
kty: "RSA";
|
|
14
|
+
n: string;
|
|
15
|
+
e: string;
|
|
16
|
+
alg?: "RS256" | "RS384" | "RS512" | "PS256" | "PS384" | "PS512" | undefined;
|
|
17
|
+
kid?: string | undefined;
|
|
18
|
+
ext?: boolean | undefined;
|
|
19
|
+
use?: "sig" | "enc" | undefined;
|
|
20
|
+
key_ops?: ("sign" | "verify" | "encrypt" | "decrypt" | "wrapKey" | "unwrapKey" | "deriveKey" | "deriveBits")[] | undefined;
|
|
21
|
+
x5c?: string[] | undefined;
|
|
22
|
+
x5t?: string | undefined;
|
|
23
|
+
"x5t#S256"?: string | undefined;
|
|
24
|
+
x5u?: string | undefined;
|
|
25
|
+
d?: string | undefined;
|
|
26
|
+
p?: string | undefined;
|
|
27
|
+
q?: string | undefined;
|
|
28
|
+
dp?: string | undefined;
|
|
29
|
+
dq?: string | undefined;
|
|
30
|
+
qi?: string | undefined;
|
|
31
|
+
oth?: [{
|
|
32
|
+
d?: string | undefined;
|
|
33
|
+
r?: string | undefined;
|
|
34
|
+
t?: string | undefined;
|
|
35
|
+
}, ...{
|
|
36
|
+
d?: string | undefined;
|
|
37
|
+
r?: string | undefined;
|
|
38
|
+
t?: string | undefined;
|
|
39
|
+
}[]] | undefined;
|
|
40
|
+
} | {
|
|
41
|
+
kty: "EC";
|
|
42
|
+
crv: "P-256" | "P-384" | "P-521";
|
|
43
|
+
x: string;
|
|
44
|
+
y: string;
|
|
45
|
+
alg?: "ES256" | "ES384" | "ES512" | undefined;
|
|
46
|
+
kid?: string | undefined;
|
|
47
|
+
ext?: boolean | undefined;
|
|
48
|
+
use?: "sig" | "enc" | undefined;
|
|
49
|
+
key_ops?: ("sign" | "verify" | "encrypt" | "decrypt" | "wrapKey" | "unwrapKey" | "deriveKey" | "deriveBits")[] | undefined;
|
|
50
|
+
x5c?: string[] | undefined;
|
|
51
|
+
x5t?: string | undefined;
|
|
52
|
+
"x5t#S256"?: string | undefined;
|
|
53
|
+
x5u?: string | undefined;
|
|
54
|
+
d?: string | undefined;
|
|
55
|
+
} | {
|
|
56
|
+
kty: "EC";
|
|
57
|
+
crv: "secp256k1";
|
|
58
|
+
x: string;
|
|
59
|
+
y: string;
|
|
60
|
+
alg?: "ES256K" | undefined;
|
|
61
|
+
kid?: string | undefined;
|
|
62
|
+
ext?: boolean | undefined;
|
|
63
|
+
use?: "sig" | "enc" | undefined;
|
|
64
|
+
key_ops?: ("sign" | "verify" | "encrypt" | "decrypt" | "wrapKey" | "unwrapKey" | "deriveKey" | "deriveBits")[] | undefined;
|
|
65
|
+
x5c?: string[] | undefined;
|
|
66
|
+
x5t?: string | undefined;
|
|
67
|
+
"x5t#S256"?: string | undefined;
|
|
68
|
+
x5u?: string | undefined;
|
|
69
|
+
d?: string | undefined;
|
|
70
|
+
} | {
|
|
71
|
+
kty: "OKP";
|
|
72
|
+
crv: "Ed25519" | "Ed448";
|
|
73
|
+
x: string;
|
|
74
|
+
alg?: "EdDSA" | undefined;
|
|
75
|
+
kid?: string | undefined;
|
|
76
|
+
ext?: boolean | undefined;
|
|
77
|
+
use?: "sig" | "enc" | undefined;
|
|
78
|
+
key_ops?: ("sign" | "verify" | "encrypt" | "decrypt" | "wrapKey" | "unwrapKey" | "deriveKey" | "deriveBits")[] | undefined;
|
|
79
|
+
x5c?: string[] | undefined;
|
|
80
|
+
x5t?: string | undefined;
|
|
81
|
+
"x5t#S256"?: string | undefined;
|
|
82
|
+
x5u?: string | undefined;
|
|
83
|
+
d?: string | undefined;
|
|
84
|
+
} | {
|
|
85
|
+
kty: "oct";
|
|
86
|
+
k: string;
|
|
87
|
+
alg?: "HS256" | "HS384" | "HS512" | undefined;
|
|
88
|
+
kid?: string | undefined;
|
|
89
|
+
ext?: boolean | undefined;
|
|
90
|
+
use?: "sig" | "enc" | undefined;
|
|
91
|
+
key_ops?: ("sign" | "verify" | "encrypt" | "decrypt" | "wrapKey" | "unwrapKey" | "deriveKey" | "deriveBits")[] | undefined;
|
|
92
|
+
x5c?: string[] | undefined;
|
|
93
|
+
x5t?: string | undefined;
|
|
94
|
+
"x5t#S256"?: string | undefined;
|
|
95
|
+
x5u?: string | undefined;
|
|
96
|
+
} | {
|
|
97
|
+
kty: string;
|
|
98
|
+
alg?: string | undefined;
|
|
99
|
+
kid?: string | undefined;
|
|
100
|
+
ext?: boolean | undefined;
|
|
101
|
+
use?: "sig" | "enc" | undefined;
|
|
102
|
+
key_ops?: ("sign" | "verify" | "encrypt" | "decrypt" | "wrapKey" | "unwrapKey" | "deriveKey" | "deriveBits")[] | undefined;
|
|
103
|
+
x5c?: string[] | undefined;
|
|
104
|
+
x5t?: string | undefined;
|
|
105
|
+
"x5t#S256"?: string | undefined;
|
|
106
|
+
x5u?: string | undefined;
|
|
107
|
+
})[];
|
|
108
|
+
} | undefined;
|
|
109
|
+
application_type?: "web" | "native" | undefined;
|
|
110
|
+
subject_type?: "public" | "pairwise" | undefined;
|
|
111
|
+
request_object_signing_alg?: string | undefined;
|
|
112
|
+
id_token_signed_response_alg?: string | undefined;
|
|
113
|
+
authorization_signed_response_alg?: string | undefined;
|
|
114
|
+
authorization_encrypted_response_enc?: "A128CBC-HS256" | undefined;
|
|
115
|
+
authorization_encrypted_response_alg?: string | undefined;
|
|
116
|
+
client_id?: string | undefined;
|
|
117
|
+
client_name?: string | undefined;
|
|
118
|
+
client_uri?: string | undefined;
|
|
119
|
+
policy_uri?: string | undefined;
|
|
120
|
+
tos_uri?: string | undefined;
|
|
121
|
+
logo_uri?: string | undefined;
|
|
122
|
+
default_max_age?: number | undefined;
|
|
123
|
+
require_auth_time?: boolean | undefined;
|
|
124
|
+
contacts?: string[] | undefined;
|
|
125
|
+
tls_client_certificate_bound_access_tokens?: boolean | undefined;
|
|
126
|
+
dpop_bound_access_tokens?: boolean | undefined;
|
|
127
|
+
authorization_details_types?: string[] | undefined;
|
|
128
|
+
}>;
|
|
129
|
+
export default _default;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineEventHandler, createError } from "h3";
|
|
2
|
+
import { getAtprotoClientMetadata } from "../../utils/atproto.js";
|
|
3
|
+
import { atprotoProviders, getClientMetadataFilename } from "../../../../utils/atproto";
|
|
4
|
+
import { useRuntimeConfig } from "#imports";
|
|
5
|
+
export default defineEventHandler((event) => {
|
|
6
|
+
const path = event.path.slice(1);
|
|
7
|
+
const runtimeConfig = useRuntimeConfig(event);
|
|
8
|
+
for (const provider of atprotoProviders) {
|
|
9
|
+
const config = runtimeConfig.oauth[provider];
|
|
10
|
+
if (getClientMetadataFilename(provider, config) === path) {
|
|
11
|
+
return getAtprotoClientMetadata(event, provider);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
throw createError({
|
|
15
|
+
statusCode: 404,
|
|
16
|
+
message: "Provider not found"
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
2
|
+
import type { OAuthClientMetadataInput } from '@atproto/oauth-client-node';
|
|
3
|
+
import type { OAuthBlueskyConfig } from '../lib/oauth/bluesky.js';
|
|
4
|
+
import type { ATProtoProvider, OAuthConfig } from '#auth-utils';
|
|
5
|
+
export declare function getAtprotoClientMetadata(event: H3Event, provider: ATProtoProvider, config?: OAuthConfig<OAuthBlueskyConfig>['config']): OAuthClientMetadataInput;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getOAuthRedirectURL } from "../lib/utils.js";
|
|
2
|
+
import { getClientMetadataFilename } from "../../../utils/atproto";
|
|
3
|
+
import { getRequestURL, useRuntimeConfig } from "#imports";
|
|
4
|
+
export function getAtprotoClientMetadata(event, provider, config) {
|
|
5
|
+
const providerRuntimeConfig = useRuntimeConfig(event).oauth[provider];
|
|
6
|
+
const scopes = [.../* @__PURE__ */ new Set(["atproto", ...config?.scope ?? [], ...providerRuntimeConfig.scope ?? []])];
|
|
7
|
+
const scope = scopes.join(" ");
|
|
8
|
+
const grantTypes = [.../* @__PURE__ */ new Set(["authorization_code", ...providerRuntimeConfig.grantTypes ?? []])];
|
|
9
|
+
const requestURL = getRequestURL(event);
|
|
10
|
+
const baseUrl = `${requestURL.protocol}//${requestURL.host}`;
|
|
11
|
+
const redirectURL = new URL(
|
|
12
|
+
config?.redirectUrl && baseUrl + config.redirectUrl || providerRuntimeConfig.redirectUris[0] && baseUrl + providerRuntimeConfig.redirectUris[0] || getOAuthRedirectURL(event)
|
|
13
|
+
);
|
|
14
|
+
const dev = import.meta.dev;
|
|
15
|
+
if (dev && redirectURL.hostname === "localhost") {
|
|
16
|
+
redirectURL.hostname = "127.0.0.1";
|
|
17
|
+
}
|
|
18
|
+
const redirectUris = (providerRuntimeConfig.redirectUris.length ? providerRuntimeConfig.redirectUris : [requestURL.pathname]).map((uri) => new URL(`${redirectURL.protocol}//${redirectURL.host}${uri}`).toString());
|
|
19
|
+
const clientId = dev ? `http://localhost?redirect_uri=${encodeURIComponent(redirectURL.toString())}&scope=${encodeURIComponent(scope)}` : `${baseUrl}/${getClientMetadataFilename("bluesky", providerRuntimeConfig)}`;
|
|
20
|
+
const clientMetadata = {
|
|
21
|
+
client_name: providerRuntimeConfig.clientName || void 0,
|
|
22
|
+
client_uri: providerRuntimeConfig.clientUri || void 0,
|
|
23
|
+
logo_uri: providerRuntimeConfig.logoUri || void 0,
|
|
24
|
+
policy_uri: providerRuntimeConfig.policyUri || void 0,
|
|
25
|
+
tos_uri: providerRuntimeConfig.tosUri || void 0,
|
|
26
|
+
client_id: clientId,
|
|
27
|
+
redirect_uris: redirectUris,
|
|
28
|
+
scope,
|
|
29
|
+
grant_types: grantTypes,
|
|
30
|
+
application_type: providerRuntimeConfig.applicationType,
|
|
31
|
+
token_endpoint_auth_method: providerRuntimeConfig.tokenEndpointAuthMethod,
|
|
32
|
+
dpop_bound_access_tokens: true
|
|
33
|
+
};
|
|
34
|
+
return clientMetadata;
|
|
35
|
+
}
|
|
@@ -20,7 +20,11 @@ export declare const sessionHooks: import("hookable").Hookable<SessionHooks, imp
|
|
|
20
20
|
* @param event The Request (h3) event
|
|
21
21
|
* @returns The user session
|
|
22
22
|
*/
|
|
23
|
-
export declare function getUserSession(event: UseSessionEvent): Promise<
|
|
23
|
+
export declare function getUserSession(event: UseSessionEvent): Promise<{
|
|
24
|
+
id: string;
|
|
25
|
+
user?: import("#auth-utils").User;
|
|
26
|
+
secure?: import("#auth-utils").SecureSessionData;
|
|
27
|
+
}>;
|
|
24
28
|
/**
|
|
25
29
|
* Set a user session
|
|
26
30
|
* @param event The Request (h3) event
|
|
@@ -4,7 +4,11 @@ import { createHooks } from "hookable";
|
|
|
4
4
|
import { useRuntimeConfig } from "#imports";
|
|
5
5
|
export const sessionHooks = createHooks();
|
|
6
6
|
export async function getUserSession(event) {
|
|
7
|
-
|
|
7
|
+
const session = await _useSession(event);
|
|
8
|
+
return {
|
|
9
|
+
id: session.id,
|
|
10
|
+
...session.data
|
|
11
|
+
};
|
|
8
12
|
}
|
|
9
13
|
export async function setUserSession(event, data, config) {
|
|
10
14
|
const session = await _useSession(event, config);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { OAuthClientMetadata } from '@atproto/oauth-client-node';
|
|
2
|
+
export interface AtprotoProviderClientMetadata {
|
|
3
|
+
/**
|
|
4
|
+
* The name of the client metadata file. This is used to construct the client ID.
|
|
5
|
+
* @example 'client-metadata.json'
|
|
6
|
+
* @example 'bluesky/client-metadata.json'
|
|
7
|
+
*/
|
|
8
|
+
clientMetadataFilename: string;
|
|
9
|
+
/**
|
|
10
|
+
* The human-readable name of the client.
|
|
11
|
+
*/
|
|
12
|
+
clientName?: OAuthClientMetadata['client_name'];
|
|
13
|
+
/**
|
|
14
|
+
* Not to be confused with client_id, this is a homepage URL for the client. If provided, the client_uri must have the same hostname as client_id.
|
|
15
|
+
*/
|
|
16
|
+
clientUri?: OAuthClientMetadata['client_uri'];
|
|
17
|
+
/**
|
|
18
|
+
* The client's logo URL.
|
|
19
|
+
*/
|
|
20
|
+
logoUri?: OAuthClientMetadata['logo_uri'];
|
|
21
|
+
/**
|
|
22
|
+
* URL to human-readable terms of service (ToS) for the client. Only https: URIs are allowed.
|
|
23
|
+
*/
|
|
24
|
+
tosUri?: OAuthClientMetadata['tos_uri'];
|
|
25
|
+
/**
|
|
26
|
+
* URL to human-readable privacy policy for the client. Only https: URIs are allowed.
|
|
27
|
+
*/
|
|
28
|
+
policyUri?: OAuthClientMetadata['policy_uri'];
|
|
29
|
+
/**
|
|
30
|
+
* Must be one of web or native, with web as the default if not specified. Note that this is field specified by OpenID/OIDC, which we are borrowing. Used by the Authorization Server to enforce the relevant "best current practices".
|
|
31
|
+
* @default 'web'
|
|
32
|
+
*/
|
|
33
|
+
applicationType?: OAuthClientMetadata['application_type'];
|
|
34
|
+
/**
|
|
35
|
+
* `authorization_code` must always be included and will be added if missing. `refresh_token` is optional, but must be included if the client will make token refresh requests.
|
|
36
|
+
* @default ['authorization_code']
|
|
37
|
+
*/
|
|
38
|
+
grantTypes?: OAuthClientMetadata['grant_types'];
|
|
39
|
+
/**
|
|
40
|
+
* All scope values which might be requested by this client are declared here. The atproto scope is required, and will be added if missing.
|
|
41
|
+
* @default ['atproto']
|
|
42
|
+
*/
|
|
43
|
+
scope?: NonNullable<OAuthClientMetadata['scope']>[];
|
|
44
|
+
/**
|
|
45
|
+
* `code` must always be included and will be added if missing.
|
|
46
|
+
*/
|
|
47
|
+
responseTypes?: OAuthClientMetadata['response_types'];
|
|
48
|
+
/**
|
|
49
|
+
* At least one redirect URI is required. The URL origin must match that of the `clientId`, so declare only the path.
|
|
50
|
+
* @example ['/auth/callback']
|
|
51
|
+
*/
|
|
52
|
+
redirectUris: OAuthClientMetadata['redirect_uris'];
|
|
53
|
+
/**
|
|
54
|
+
* The token endpoint authentication method. `none` is the default, and the only supported value at this time.
|
|
55
|
+
* @default 'none'
|
|
56
|
+
*/
|
|
57
|
+
tokenEndpointAuthMethod?: OAuthClientMetadata['token_endpoint_auth_method'];
|
|
58
|
+
/**
|
|
59
|
+
* DPoP is mandatory for all clients, so this must be present and true.
|
|
60
|
+
*/
|
|
61
|
+
dpopBoundAccessTokens?: OAuthClientMetadata['dpop_bound_access_tokens'];
|
|
62
|
+
}
|
|
File without changes
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type { User, UserSession, UserSessionRequired, UserSessionComposable, SecureSessionData } from './session.js';
|
|
2
|
-
export type { OAuthConfig, OAuthProvider, OnError } from './oauth-config.js';
|
|
2
|
+
export type { OAuthConfig, OAuthProvider, ATProtoProvider, OnError } from './oauth-config.js';
|
|
3
3
|
export type { WebAuthnCredential, WebAuthnRegisterEventHandlerOptions, WebAuthnAuthenticateEventHandlerOptions, WebAuthnComposable, WebAuthnUser, } from './webauthn.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { H3Event, H3Error } from 'h3';
|
|
2
|
-
export type
|
|
2
|
+
export type ATProtoProvider = 'bluesky';
|
|
3
|
+
export type OAuthProvider = ATProtoProvider | 'atlassian' | 'auth0' | 'authentik' | 'battledotnet' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'gitea' | 'github' | 'gitlab' | 'google' | 'hubspot' | 'instagram' | 'keycloak' | 'line' | 'linear' | 'linkedin' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'seznam' | 'steam' | 'strava' | 'tiktok' | 'twitch' | 'vk' | 'workos' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | 'apple' | (string & {});
|
|
3
4
|
export type OnError = (event: H3Event, error: H3Error) => Promise<void> | void;
|
|
4
5
|
export interface OAuthConfig<TConfig, TUser = any, TTokens = any> {
|
|
5
6
|
config?: TConfig;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-auth-utils",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.12",
|
|
4
4
|
"description": "Add Authentication to Nuxt applications with secured & sealed cookies sessions.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@adonisjs/hash": "^9.0.5",
|
|
39
|
-
"@nuxt/kit": "^3.15.
|
|
39
|
+
"@nuxt/kit": "^3.15.4",
|
|
40
40
|
"defu": "^6.1.4",
|
|
41
41
|
"h3": "^1.14.0",
|
|
42
42
|
"hookable": "^5.5.3",
|
|
@@ -49,6 +49,8 @@
|
|
|
49
49
|
"uncrypto": "^0.1.3"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
+
"@atproto/api": "^0.13.15",
|
|
53
|
+
"@atproto/oauth-client-node": "^0.2.0",
|
|
52
54
|
"@simplewebauthn/browser": "^11.0.0",
|
|
53
55
|
"@simplewebauthn/server": "^11.0.0"
|
|
54
56
|
},
|
|
@@ -58,23 +60,29 @@
|
|
|
58
60
|
},
|
|
59
61
|
"@simplewebauthn/server": {
|
|
60
62
|
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"@atproto/oauth-client-node": {
|
|
65
|
+
"optional": true
|
|
66
|
+
},
|
|
67
|
+
"@atproto/api": {
|
|
68
|
+
"optional": true
|
|
61
69
|
}
|
|
62
70
|
},
|
|
63
71
|
"devDependencies": {
|
|
64
|
-
"@iconify-json/simple-icons": "^1.2.
|
|
65
|
-
"@nuxt/devtools": "
|
|
66
|
-
"@nuxt/eslint-config": "^0.7.
|
|
72
|
+
"@iconify-json/simple-icons": "^1.2.23",
|
|
73
|
+
"@nuxt/devtools": "2.0.0-beta.7",
|
|
74
|
+
"@nuxt/eslint-config": "^0.7.6",
|
|
67
75
|
"@nuxt/module-builder": "^0.8.4",
|
|
68
|
-
"@nuxt/schema": "^3.15.
|
|
76
|
+
"@nuxt/schema": "^3.15.4",
|
|
69
77
|
"@nuxt/test-utils": "^3.15.4",
|
|
70
78
|
"@nuxt/ui": "^2.21.0",
|
|
71
79
|
"@nuxt/ui-pro": "^1.7.0",
|
|
72
80
|
"@simplewebauthn/types": "^12.0.0",
|
|
73
81
|
"changelogen": "^0.5.7",
|
|
74
|
-
"eslint": "^9.
|
|
75
|
-
"nuxt": "^3.15.
|
|
82
|
+
"eslint": "^9.19.0",
|
|
83
|
+
"nuxt": "^3.15.4",
|
|
76
84
|
"typescript": "5.6.3",
|
|
77
|
-
"vitest": "^3.0.
|
|
85
|
+
"vitest": "^3.0.5",
|
|
78
86
|
"vue-tsc": "^2.2.0"
|
|
79
87
|
}
|
|
80
88
|
}
|