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 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
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "auth-utils",
3
3
  "configKey": "auth",
4
- "version": "0.0.9"
4
+ "version": "0.0.11"
5
5
  }
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
- return await requireUserSession(event);
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.9",
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.79",
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.10.0",
46
- "@nuxt/ui-pro": "^0.4.2",
47
- "@types/node": "^20.9.3",
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.54.0",
50
+ "eslint": "^8.55.0",
50
51
  "nuxt": "^3.8.2",
51
- "typescript": "^5.3.2",
52
- "vitest": "^0.34.6",
53
- "vue-tsc": "^1.8.22"
52
+ "typescript": "^5.3.3",
53
+ "vitest": "^1.0.2",
54
+ "vue-tsc": "^1.8.25"
54
55
  }
55
56
  }