nuxt-auth-utils 0.0.9 → 0.0.11
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 +20 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +7 -1
- package/dist/runtime/server/api/session.get.mjs +4 -2
- package/dist/runtime/server/lib/oauth/linkedin.d.ts +44 -0
- package/dist/runtime/server/lib/oauth/linkedin.mjs +83 -0
- package/dist/runtime/server/utils/oauth.d.ts +2 -0
- package/dist/runtime/server/utils/oauth.mjs +3 -1
- package/dist/runtime/server/utils/session.d.ts +13 -0
- package/dist/runtime/server/utils/session.mjs +3 -0
- package/package.json +10 -9
package/README.md
CHANGED
|
@@ -154,6 +154,7 @@ It can also be set using environment variables:
|
|
|
154
154
|
- Discord
|
|
155
155
|
- GitHub
|
|
156
156
|
- Google
|
|
157
|
+
- LinkedIn
|
|
157
158
|
- Microsoft
|
|
158
159
|
- Spotify
|
|
159
160
|
- Twitch
|
|
@@ -187,6 +188,25 @@ export default oauth.githubEventHandler({
|
|
|
187
188
|
|
|
188
189
|
Make sure to set the callback URL in your OAuth app settings as `<your-domain>/auth/github`.
|
|
189
190
|
|
|
191
|
+
### Extend Session
|
|
192
|
+
|
|
193
|
+
We leverage hooks to let you extend the session data with your own data or to log when the user clear its session.
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
// server/plugins/session.ts
|
|
197
|
+
export default defineNitroPlugin(() => {
|
|
198
|
+
sessionHooks.hook('verify', async (session, event) => {
|
|
199
|
+
// extend User Session by calling your database
|
|
200
|
+
// or
|
|
201
|
+
// throw createError({ ... }) if session is invalid for example
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
sessionHooks.hook('clear', async (session, event) => {
|
|
205
|
+
// Log that user logged out
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
|
+
```
|
|
209
|
+
|
|
190
210
|
## Development
|
|
191
211
|
|
|
192
212
|
```bash
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -30,6 +30,7 @@ const module = defineNuxtModule({
|
|
|
30
30
|
{
|
|
31
31
|
from: resolver.resolve("./runtime/server/utils/session"),
|
|
32
32
|
imports: [
|
|
33
|
+
"sessionHooks",
|
|
33
34
|
"getUserSession",
|
|
34
35
|
"setUserSession",
|
|
35
36
|
"clearUserSession",
|
|
@@ -77,7 +78,8 @@ const module = defineNuxtModule({
|
|
|
77
78
|
runtimeConfig.oauth.auth0 = defu(runtimeConfig.oauth.auth0, {
|
|
78
79
|
clientId: "",
|
|
79
80
|
clientSecret: "",
|
|
80
|
-
domain: ""
|
|
81
|
+
domain: "",
|
|
82
|
+
audience: ""
|
|
81
83
|
});
|
|
82
84
|
runtimeConfig.oauth.microsoft = defu(runtimeConfig.oauth.microsoft, {
|
|
83
85
|
clientId: "",
|
|
@@ -96,6 +98,10 @@ const module = defineNuxtModule({
|
|
|
96
98
|
clientId: "",
|
|
97
99
|
clientSecret: ""
|
|
98
100
|
});
|
|
101
|
+
runtimeConfig.oauth.linkedin = defu(runtimeConfig.oauth.linkedin, {
|
|
102
|
+
clientId: "",
|
|
103
|
+
clientSecret: ""
|
|
104
|
+
});
|
|
99
105
|
}
|
|
100
106
|
});
|
|
101
107
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { eventHandler } from "h3";
|
|
2
|
-
import { requireUserSession } from "../utils/session.mjs";
|
|
2
|
+
import { requireUserSession, sessionHooks } from "../utils/session.mjs";
|
|
3
3
|
export default eventHandler(async (event) => {
|
|
4
|
-
|
|
4
|
+
const session = await requireUserSession(event);
|
|
5
|
+
await sessionHooks.callHookParallel("verify", event, session);
|
|
6
|
+
return session;
|
|
5
7
|
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { H3Event, H3Error } from 'h3';
|
|
2
|
+
export interface OAuthLinkedInConfig {
|
|
3
|
+
/**
|
|
4
|
+
* LinkedIn OAuth Client ID
|
|
5
|
+
* @default process.env.NUXT_OAUTH_LINKEDIN_CLIENT_ID
|
|
6
|
+
*/
|
|
7
|
+
clientId?: string;
|
|
8
|
+
/**
|
|
9
|
+
* LinkedIn OAuth Client Secret
|
|
10
|
+
* @default process.env.NUXT_OAUTH_LINKEDIN_CLIENT_SECRET
|
|
11
|
+
*/
|
|
12
|
+
clientSecret?: string;
|
|
13
|
+
/**
|
|
14
|
+
* LinkedIn OAuth Scope
|
|
15
|
+
* @default ['openid', 'profile', 'email']
|
|
16
|
+
* @example ['openid', 'profile']
|
|
17
|
+
*/
|
|
18
|
+
scope?: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Require email from user, adds the ['email'] scope if not present
|
|
21
|
+
* @default false
|
|
22
|
+
*/
|
|
23
|
+
emailRequired?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* LinkedIn OAuth Authorization URL
|
|
26
|
+
* @default 'https://www.linkedin.com/oauth/v2/authorization'
|
|
27
|
+
*/
|
|
28
|
+
authorizationURL?: string;
|
|
29
|
+
/**
|
|
30
|
+
* LinkedIn OAuth Token URL
|
|
31
|
+
* @default 'https://www.linkedin.com/oauth/v2/accessToken'
|
|
32
|
+
*/
|
|
33
|
+
tokenURL?: string;
|
|
34
|
+
}
|
|
35
|
+
interface OAuthConfig {
|
|
36
|
+
config?: OAuthLinkedInConfig;
|
|
37
|
+
onSuccess: (event: H3Event, result: {
|
|
38
|
+
user: any;
|
|
39
|
+
tokens: any;
|
|
40
|
+
}) => Promise<void> | void;
|
|
41
|
+
onError?: (event: H3Event, error: H3Error) => Promise<void> | void;
|
|
42
|
+
}
|
|
43
|
+
export declare function linkedinEventHandler({ config, onSuccess, onError }: OAuthConfig): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { eventHandler, createError, getQuery, getRequestURL, sendRedirect } from "h3";
|
|
2
|
+
import { withQuery, parseURL, stringifyParsedURL } from "ufo";
|
|
3
|
+
import { ofetch } from "ofetch";
|
|
4
|
+
import { defu } from "defu";
|
|
5
|
+
import { useRuntimeConfig } from "#imports";
|
|
6
|
+
export function linkedinEventHandler({ config, onSuccess, onError }) {
|
|
7
|
+
return eventHandler(async (event) => {
|
|
8
|
+
config = defu(config, useRuntimeConfig(event).oauth?.linkedin, {
|
|
9
|
+
authorizationURL: "https://www.linkedin.com/oauth/v2/authorization",
|
|
10
|
+
tokenURL: "https://www.linkedin.com/oauth/v2/accessToken"
|
|
11
|
+
});
|
|
12
|
+
const { code } = getQuery(event);
|
|
13
|
+
if (!config.clientId || !config.clientSecret) {
|
|
14
|
+
const error = createError({
|
|
15
|
+
statusCode: 500,
|
|
16
|
+
message: "Missing NUXT_OAUTH_LINKEDIN_CLIENT_ID or NUXT_OAUTH_LINKEDIN_CLIENT_SECRET env variables."
|
|
17
|
+
});
|
|
18
|
+
if (!onError)
|
|
19
|
+
throw error;
|
|
20
|
+
return onError(event, error);
|
|
21
|
+
}
|
|
22
|
+
const redirectUrl = getRequestURL(event).href;
|
|
23
|
+
if (!code) {
|
|
24
|
+
config.scope = config.scope || [];
|
|
25
|
+
if (!config.scope.length) {
|
|
26
|
+
config.scope.push("profile", "openid", "email");
|
|
27
|
+
}
|
|
28
|
+
if (config.emailRequired && !config.scope.includes("email")) {
|
|
29
|
+
config.scope.push("email");
|
|
30
|
+
}
|
|
31
|
+
return sendRedirect(
|
|
32
|
+
event,
|
|
33
|
+
withQuery(config.authorizationURL, {
|
|
34
|
+
response_type: "code",
|
|
35
|
+
client_id: config.clientId,
|
|
36
|
+
redirect_uri: redirectUrl,
|
|
37
|
+
scope: config.scope.join(" ")
|
|
38
|
+
})
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
const parsedRedirectUrl = parseURL(redirectUrl);
|
|
42
|
+
parsedRedirectUrl.search = "";
|
|
43
|
+
const tokens = await ofetch(
|
|
44
|
+
config.tokenURL,
|
|
45
|
+
{
|
|
46
|
+
method: "POST",
|
|
47
|
+
headers: {
|
|
48
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
49
|
+
},
|
|
50
|
+
body: new URLSearchParams({
|
|
51
|
+
grant_type: "authorization_code",
|
|
52
|
+
code,
|
|
53
|
+
client_id: config.clientId,
|
|
54
|
+
client_secret: config.clientSecret,
|
|
55
|
+
redirect_uri: stringifyParsedURL(parsedRedirectUrl)
|
|
56
|
+
}).toString()
|
|
57
|
+
}
|
|
58
|
+
).catch((error) => {
|
|
59
|
+
return { error };
|
|
60
|
+
});
|
|
61
|
+
if (tokens.error) {
|
|
62
|
+
const error = createError({
|
|
63
|
+
statusCode: 401,
|
|
64
|
+
message: `LinkedIn login failed: ${tokens.error?.data?.error_description || "Unknown error"}`,
|
|
65
|
+
data: tokens
|
|
66
|
+
});
|
|
67
|
+
if (!onError)
|
|
68
|
+
throw error;
|
|
69
|
+
return onError(event, error);
|
|
70
|
+
}
|
|
71
|
+
const accessToken = tokens.access_token;
|
|
72
|
+
const user = await ofetch("https://api.linkedin.com/v2/userinfo", {
|
|
73
|
+
headers: {
|
|
74
|
+
"user-agent": "Nuxt Auth Utils",
|
|
75
|
+
Authorization: `Bearer ${accessToken}`
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
return onSuccess(event, {
|
|
79
|
+
tokens,
|
|
80
|
+
user
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
@@ -6,6 +6,7 @@ import { auth0EventHandler } from '../lib/oauth/auth0';
|
|
|
6
6
|
import { microsoftEventHandler } from '../lib/oauth/microsoft';
|
|
7
7
|
import { discordEventHandler } from '../lib/oauth/discord';
|
|
8
8
|
import { battledotnetEventHandler } from '../lib/oauth/battledotnet';
|
|
9
|
+
import { linkedinEventHandler } from '../lib/oauth/linkedin';
|
|
9
10
|
export declare const oauth: {
|
|
10
11
|
githubEventHandler: typeof githubEventHandler;
|
|
11
12
|
spotifyEventHandler: typeof spotifyEventHandler;
|
|
@@ -15,4 +16,5 @@ export declare const oauth: {
|
|
|
15
16
|
microsoftEventHandler: typeof microsoftEventHandler;
|
|
16
17
|
discordEventHandler: typeof discordEventHandler;
|
|
17
18
|
battledotnetEventHandler: typeof battledotnetEventHandler;
|
|
19
|
+
linkedinEventHandler: typeof linkedinEventHandler;
|
|
18
20
|
};
|
|
@@ -6,6 +6,7 @@ import { auth0EventHandler } from "../lib/oauth/auth0.mjs";
|
|
|
6
6
|
import { microsoftEventHandler } from "../lib/oauth/microsoft.mjs";
|
|
7
7
|
import { discordEventHandler } from "../lib/oauth/discord.mjs";
|
|
8
8
|
import { battledotnetEventHandler } from "../lib/oauth/battledotnet.mjs";
|
|
9
|
+
import { linkedinEventHandler } from "../lib/oauth/linkedin.mjs";
|
|
9
10
|
export const oauth = {
|
|
10
11
|
githubEventHandler,
|
|
11
12
|
spotifyEventHandler,
|
|
@@ -14,5 +15,6 @@ export const oauth = {
|
|
|
14
15
|
auth0EventHandler,
|
|
15
16
|
microsoftEventHandler,
|
|
16
17
|
discordEventHandler,
|
|
17
|
-
battledotnetEventHandler
|
|
18
|
+
battledotnetEventHandler,
|
|
19
|
+
linkedinEventHandler
|
|
18
20
|
};
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import type { H3Event } from 'h3';
|
|
2
2
|
import type { UserSession } from '#auth-utils';
|
|
3
|
+
export interface SessionHooks {
|
|
4
|
+
/**
|
|
5
|
+
* Called when fetching the session from the API
|
|
6
|
+
* - Add extra properties to the session
|
|
7
|
+
* - Throw an error if the session could not be verified (with a database for example)
|
|
8
|
+
*/
|
|
9
|
+
'verify': (session: UserSession, event: H3Event) => void | Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Called before clearing the session
|
|
12
|
+
*/
|
|
13
|
+
'clear': (session: UserSession, event: H3Event) => void | Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export declare const sessionHooks: import("hookable").Hookable<SessionHooks, import("hookable").HookKeys<SessionHooks>>;
|
|
3
16
|
export declare function getUserSession(event: H3Event): Promise<UserSession>;
|
|
4
17
|
/**
|
|
5
18
|
* Set a user session
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { useSession, createError } from "h3";
|
|
2
2
|
import { defu } from "defu";
|
|
3
|
+
import { createHooks } from "hookable";
|
|
3
4
|
import { useRuntimeConfig } from "#imports";
|
|
5
|
+
export const sessionHooks = createHooks();
|
|
4
6
|
export async function getUserSession(event) {
|
|
5
7
|
return (await _useSession(event)).data;
|
|
6
8
|
}
|
|
@@ -11,6 +13,7 @@ export async function setUserSession(event, data) {
|
|
|
11
13
|
}
|
|
12
14
|
export async function clearUserSession(event) {
|
|
13
15
|
const session = await _useSession(event);
|
|
16
|
+
await sessionHooks.callHookParallel("clear", event, session);
|
|
14
17
|
await session.clear();
|
|
15
18
|
return true;
|
|
16
19
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-auth-utils",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"description": "Minimalist Auth module for Nuxt with SSR",
|
|
5
5
|
"repository": "Atinux/nuxt-auth-utils",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,24 +32,25 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@nuxt/kit": "^3.8.2",
|
|
34
34
|
"defu": "^6.1.3",
|
|
35
|
+
"hookable": "^5.5.3",
|
|
35
36
|
"ofetch": "^1.3.3",
|
|
36
37
|
"ohash": "^1.1.3"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
|
-
"@iconify-json/simple-icons": "^1.1.
|
|
40
|
+
"@iconify-json/simple-icons": "^1.1.82",
|
|
40
41
|
"@nuxt/devtools": "latest",
|
|
41
42
|
"@nuxt/eslint-config": "^0.2.0",
|
|
42
43
|
"@nuxt/module-builder": "^0.5.4",
|
|
43
44
|
"@nuxt/schema": "^3.8.2",
|
|
44
45
|
"@nuxt/test-utils": "^3.8.1",
|
|
45
|
-
"@nuxt/ui": "^2.
|
|
46
|
-
"@nuxt/ui-pro": "^0.
|
|
47
|
-
"@types/node": "^20.
|
|
46
|
+
"@nuxt/ui": "^2.11.0",
|
|
47
|
+
"@nuxt/ui-pro": "^0.5.0",
|
|
48
|
+
"@types/node": "^20.10.4",
|
|
48
49
|
"changelogen": "^0.5.5",
|
|
49
|
-
"eslint": "^8.
|
|
50
|
+
"eslint": "^8.55.0",
|
|
50
51
|
"nuxt": "^3.8.2",
|
|
51
|
-
"typescript": "^5.3.
|
|
52
|
-
"vitest": "^0.
|
|
53
|
-
"vue-tsc": "^1.8.
|
|
52
|
+
"typescript": "^5.3.3",
|
|
53
|
+
"vitest": "^1.0.2",
|
|
54
|
+
"vue-tsc": "^1.8.25"
|
|
54
55
|
}
|
|
55
56
|
}
|