@meistrari/auth-nuxt 1.1.0 → 2.0.1

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/module.d.mts CHANGED
@@ -16,6 +16,10 @@ interface ModuleOptions {
16
16
  applicationId: string;
17
17
  /** The redirect URI to redirect to after authentication. Must be registered in the application's settings. */
18
18
  redirectUri: string;
19
+ /** Path to redirect to when authentication is required (default: '/login') */
20
+ loginPath?: string;
21
+ /** Path to redirect to when user lacks required role (default: '/unauthorized') */
22
+ unauthorizedPath?: string;
19
23
  };
20
24
  }
21
25
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meistrari/auth-nuxt",
3
3
  "configKey": "telaAuth",
4
- "version": "1.1.0",
4
+ "version": "1.0.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { defineNuxtModule, createResolver, addImports, addPlugin, addServerHandler } from '@nuxt/kit';
1
+ import { defineNuxtModule, createResolver, addImports, addPlugin, extendPages, addComponent, addRouteMiddleware, addServerImportsDir, addServerHandler } from '@nuxt/kit';
2
2
 
3
3
  const module$1 = defineNuxtModule({
4
4
  meta: {
@@ -19,6 +19,32 @@ const module$1 = defineNuxtModule({
19
19
  from: resolver.resolve("runtime/composables/application-auth")
20
20
  });
21
21
  addPlugin(resolver.resolve("./runtime/plugins/application-token-refresh"));
22
+ extendPages((pages) => {
23
+ pages.unshift({
24
+ name: "auth-callback",
25
+ path: "/auth/callback",
26
+ file: resolver.resolve("./runtime/pages/callback.vue")
27
+ });
28
+ });
29
+ addComponent({
30
+ name: "TelaRole",
31
+ filePath: resolver.resolve("./runtime/components/tela-role.vue"),
32
+ global: true,
33
+ export: "default"
34
+ });
35
+ nuxt.hook("prepare:types", ({ references }) => {
36
+ references.push({
37
+ path: resolver.resolve("./runtime/types/page-meta.d.ts")
38
+ });
39
+ });
40
+ addPlugin(resolver.resolve("./runtime/plugins/auth-guard"));
41
+ addPlugin(resolver.resolve("./runtime/plugins/directives"));
42
+ addRouteMiddleware({
43
+ name: "tela-auth",
44
+ path: resolver.resolve("./runtime/middleware/require-auth"),
45
+ global: true
46
+ });
47
+ addServerImportsDir(resolver.resolve("./runtime/server/utils"));
22
48
  return;
23
49
  }
24
50
  if (!options.skipServerMiddleware) {
@@ -0,0 +1,23 @@
1
+ type __VLS_Props = {
2
+ allowedRoles: string[];
3
+ };
4
+ declare var __VLS_1: {
5
+ role: any;
6
+ }, __VLS_3: {
7
+ role: any;
8
+ }, __VLS_5: {};
9
+ type __VLS_Slots = {} & {
10
+ default?: (props: typeof __VLS_1) => any;
11
+ } & {
12
+ 'not-authorized'?: (props: typeof __VLS_3) => any;
13
+ } & {
14
+ 'logged-out'?: (props: typeof __VLS_5) => any;
15
+ };
16
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
18
+ export default _default;
19
+ type __VLS_WithSlots<T, S> = T & {
20
+ new (): {
21
+ $slots: S;
22
+ };
23
+ };
@@ -0,0 +1,15 @@
1
+ <script setup>
2
+ import { useTelaApplicationAuth } from "../composables/application-auth";
3
+ const { user } = useTelaApplicationAuth();
4
+ defineProps({
5
+ allowedRoles: { type: Array, required: true }
6
+ });
7
+ </script>
8
+
9
+ <template>
10
+ <template v-if="user">
11
+ <slot v-if="user.role && allowedRoles.includes(user.role)" :role="user.role" />
12
+ <slot v-else name="not-authorized" :role="user.role" />
13
+ </template>
14
+ <slot v-else name="logged-out" />
15
+ </template>
@@ -0,0 +1,23 @@
1
+ type __VLS_Props = {
2
+ allowedRoles: string[];
3
+ };
4
+ declare var __VLS_1: {
5
+ role: any;
6
+ }, __VLS_3: {
7
+ role: any;
8
+ }, __VLS_5: {};
9
+ type __VLS_Slots = {} & {
10
+ default?: (props: typeof __VLS_1) => any;
11
+ } & {
12
+ 'not-authorized'?: (props: typeof __VLS_3) => any;
13
+ } & {
14
+ 'logged-out'?: (props: typeof __VLS_5) => any;
15
+ };
16
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
17
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
18
+ export default _default;
19
+ type __VLS_WithSlots<T, S> = T & {
20
+ new (): {
21
+ $slots: S;
22
+ };
23
+ };
@@ -0,0 +1,2 @@
1
+ declare const _default: import("#app").RouteMiddleware;
2
+ export default _default;
@@ -0,0 +1,42 @@
1
+ import { defineNuxtRouteMiddleware, navigateTo, useCookie, useRuntimeConfig } from "#app";
2
+ import { createRemoteJWKSet, jwtVerify } from "jose";
3
+ export default defineNuxtRouteMiddleware(async (to) => {
4
+ const authMeta = to.meta?.auth;
5
+ if (!authMeta || authMeta.required !== true) {
6
+ return;
7
+ }
8
+ const config = useRuntimeConfig();
9
+ const authConfig = config.public.telaAuth;
10
+ const { apiUrl, application } = authConfig;
11
+ const { applicationId, loginPath = "/login", unauthorizedPath = "/unauthorized" } = application ?? {};
12
+ const token = useCookie("tela-access-token");
13
+ if (!token.value) {
14
+ return navigateTo(loginPath);
15
+ }
16
+ if (!applicationId || !apiUrl) {
17
+ console.error("Application ID or API URL is not configured");
18
+ return;
19
+ }
20
+ try {
21
+ const { payload } = await jwtVerify(
22
+ token.value,
23
+ createRemoteJWKSet(new URL("/.well-known/jwks.json", apiUrl)),
24
+ {
25
+ issuer: apiUrl,
26
+ audience: applicationId,
27
+ algorithms: ["RS256"]
28
+ }
29
+ );
30
+ const tokenPayload = payload;
31
+ const user = tokenPayload.user;
32
+ if (authMeta.roles && authMeta.roles.length > 0) {
33
+ const userRole = user.role ?? "";
34
+ if (!authMeta.roles.includes(userRole)) {
35
+ return navigateTo(unauthorizedPath);
36
+ }
37
+ }
38
+ } catch (error) {
39
+ console.error("Token validation failed:", error);
40
+ return navigateTo(loginPath);
41
+ }
42
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ export default _default;
@@ -0,0 +1,16 @@
1
+ <script setup>
2
+ import { navigateTo } from "#app";
3
+ import { onMounted } from "vue";
4
+ import { useTelaApplicationAuth } from "../composables/application-auth";
5
+ const { initSession } = useTelaApplicationAuth();
6
+ onMounted(async () => {
7
+ await initSession();
8
+ navigateTo("/");
9
+ });
10
+ </script>
11
+
12
+ <template>
13
+ <div class="flex items-center justify-center min-h-screen">
14
+ <h1>Loading...</h1>
15
+ </div>
16
+ </template>
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import("#app").Plugin<Record<string, unknown>> & import("#app").ObjectPlugin<Record<string, unknown>>;
2
+ export default _default;
@@ -0,0 +1,39 @@
1
+ import { addRouteMiddleware, defineNuxtPlugin, navigateTo, useCookie, useRuntimeConfig } from "#app";
2
+ import { decodeJwt } from "jose";
3
+ export default defineNuxtPlugin(() => {
4
+ const config = useRuntimeConfig();
5
+ const authConfig = config.public.telaAuth;
6
+ const loginPath = authConfig.application?.loginPath ?? "/login";
7
+ const unauthorizedPath = authConfig.application?.unauthorizedPath ?? "/unauthorized";
8
+ addRouteMiddleware("auth-guard", (to) => {
9
+ const authMeta = to.meta?.auth;
10
+ if (!authMeta || authMeta.required !== true) {
11
+ return;
12
+ }
13
+ const token = useCookie("tela-access-token");
14
+ if (!token.value) {
15
+ return navigateTo({
16
+ path: loginPath,
17
+ query: { redirect: to.fullPath }
18
+ });
19
+ }
20
+ if (authMeta.roles && authMeta.roles.length > 0) {
21
+ try {
22
+ const payload = decodeJwt(token.value);
23
+ const userRole = payload.user?.role;
24
+ if (!userRole || !authMeta.roles.includes(userRole)) {
25
+ return navigateTo({
26
+ path: unauthorizedPath,
27
+ query: { redirect: to.fullPath }
28
+ });
29
+ }
30
+ } catch (error) {
31
+ console.error("Failed to decode token:", error);
32
+ return navigateTo({
33
+ path: loginPath,
34
+ query: { redirect: to.fullPath }
35
+ });
36
+ }
37
+ }
38
+ }, { global: true });
39
+ });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Plugin to register custom role-based directives
3
+ *
4
+ * Provides:
5
+ * - v-if-role: Conditionally renders element (like v-if) based on user role
6
+ * - v-show-role: Conditionally shows element (like v-show) based on user role
7
+ *
8
+ * @example
9
+ * ```vue
10
+ * <!-- Single role -->
11
+ * <button v-if-role="'admin'">Admin Only</button>
12
+ * <div v-show-role="'moderator'">Moderator Panel</div>
13
+ *
14
+ * <!-- Multiple roles -->
15
+ * <button v-if-role="['admin', 'moderator']">Admin or Moderator</button>
16
+ * <div v-show-role="['editor', 'writer']">Content Panel</div>
17
+ * ```
18
+ */
19
+ declare const _default: import("#app").Plugin<Record<string, unknown>> & import("#app").ObjectPlugin<Record<string, unknown>>;
20
+ export default _default;
@@ -0,0 +1,58 @@
1
+ import { defineNuxtPlugin } from "#app";
2
+ import { useApplicationSessionState } from "../composables/state.js";
3
+ function hasRole(allowedRoles) {
4
+ const { user } = useApplicationSessionState();
5
+ if (!user.value?.role) {
6
+ return false;
7
+ }
8
+ const roles = Array.isArray(allowedRoles) ? allowedRoles : [allowedRoles];
9
+ return roles.includes(user.value.role);
10
+ }
11
+ export default defineNuxtPlugin((nuxtApp) => {
12
+ nuxtApp.vueApp.directive("if-role", {
13
+ mounted(el, binding) {
14
+ const shouldRender = hasRole(binding.value);
15
+ if (!shouldRender) {
16
+ const comment = document.createComment("v-if-role");
17
+ el.parentNode?.replaceChild(comment, el);
18
+ comment.__vIfRoleElement = el;
19
+ }
20
+ },
21
+ updated(el, binding) {
22
+ const shouldRender = hasRole(binding.value);
23
+ const isComment = el.nodeType === 8;
24
+ if (shouldRender && isComment) {
25
+ const originalElement = el.__vIfRoleElement;
26
+ if (originalElement) {
27
+ el.parentNode?.replaceChild(originalElement, el);
28
+ }
29
+ } else if (!shouldRender && !isComment) {
30
+ const comment = document.createComment("v-if-role");
31
+ el.parentNode?.replaceChild(comment, el);
32
+ comment.__vIfRoleElement = el;
33
+ }
34
+ }
35
+ });
36
+ nuxtApp.vueApp.directive("show-role", {
37
+ mounted(el, binding) {
38
+ const shouldShow = hasRole(binding.value);
39
+ if (!shouldShow) {
40
+ ;
41
+ el.__vOriginalDisplay = el.style.display;
42
+ el.style.display = "none";
43
+ }
44
+ },
45
+ updated(el, binding) {
46
+ const shouldShow = hasRole(binding.value);
47
+ if (shouldShow) {
48
+ el.style.display = el.__vOriginalDisplay || "";
49
+ } else {
50
+ if (!el.__vOriginalDisplay && el.style.display !== "none") {
51
+ ;
52
+ el.__vOriginalDisplay = el.style.display;
53
+ }
54
+ el.style.display = "none";
55
+ }
56
+ }
57
+ });
58
+ });
@@ -6,6 +6,7 @@ declare module 'h3' {
6
6
  auth?: {
7
7
  user: { email: string } & JWTTokenPayload['user'] | null
8
8
  workspace: JWTTokenPayload['workspace'] | null
9
+ token?: string
9
10
  }
10
11
  }
11
12
  }
@@ -14,6 +15,7 @@ export interface AuthenticatedH3EventContext extends H3EventContext {
14
15
  auth: {
15
16
  user: { email: string } & JWTTokenPayload['user'] | null
16
17
  workspace: JWTTokenPayload['workspace'] | null
18
+ token?: string
17
19
  }
18
20
  }
19
21
 
@@ -0,0 +1,21 @@
1
+ import type { EventHandlerRequest } from 'h3';
2
+ import type { AuthenticatedH3Event } from '../types/h3.js';
3
+ /**
4
+ * Wraps an event handler to require authentication.
5
+ * Throws 401 if no valid token is present.
6
+ *
7
+ * @example
8
+ * export default requireAuth(async (event) => {
9
+ * const token = event.context.auth.token
10
+ * return { data: 'protected' }
11
+ * })
12
+ *
13
+ * @example With roles
14
+ * export default requireAuth(async (event) => {
15
+ * // TODO: Validate roles from token
16
+ * return { data: 'admin only' }
17
+ * }, { roles: ['admin'] })
18
+ */
19
+ export declare function requireAuth<T>(handler: (event: AuthenticatedH3Event) => T | Promise<T>, options?: {
20
+ roles?: string[];
21
+ }): import("h3").EventHandler<EventHandlerRequest, any>;
@@ -0,0 +1,50 @@
1
+ import { useRuntimeConfig } from "#build/types/nitro-imports";
2
+ import { createError, defineEventHandler, getCookie } from "h3";
3
+ import { createRemoteJWKSet, jwtVerify } from "jose";
4
+ export function requireAuth(handler, options) {
5
+ return defineEventHandler(async (event) => {
6
+ const token = getCookie(event, "tela-access-token");
7
+ const authConfig = useRuntimeConfig(event).public.telaAuth;
8
+ const { apiUrl, application } = authConfig;
9
+ const { applicationId } = application ?? {};
10
+ if (!applicationId) {
11
+ throw createError({
12
+ statusCode: 500,
13
+ statusMessage: "Internal Server Error",
14
+ message: "Application ID is not configured"
15
+ });
16
+ }
17
+ if (!apiUrl) {
18
+ throw createError({
19
+ statusCode: 500,
20
+ statusMessage: "Internal Server Error",
21
+ message: "API URL is not configured"
22
+ });
23
+ }
24
+ if (!token) {
25
+ throw createError({
26
+ statusCode: 401,
27
+ statusMessage: "Unauthorized",
28
+ message: "Authentication required"
29
+ });
30
+ }
31
+ const payload = await jwtVerify(token, createRemoteJWKSet(new URL("/.well-known/jwks.json", apiUrl)), {
32
+ issuer: apiUrl,
33
+ audience: applicationId,
34
+ algorithms: ["RS256"]
35
+ }).then(({ payload: payload2 }) => payload2);
36
+ const user = payload.user;
37
+ if (options?.roles && !options.roles.includes(user.role ?? "")) {
38
+ throw createError({
39
+ statusCode: 403,
40
+ statusMessage: "Forbidden",
41
+ message: "User is not authorized to access this resource"
42
+ });
43
+ }
44
+ if (!event.context.auth) {
45
+ event.context.auth = { user: { ...payload.user, email: payload.email }, workspace: payload.workspace, token };
46
+ }
47
+ event.context.auth.token = token;
48
+ return handler(event);
49
+ });
50
+ }
@@ -0,0 +1,15 @@
1
+ declare type Roles = 'meistrari:admin' | (string & {})
2
+ declare module '#app' {
3
+ interface PageMeta {
4
+ auth?: {
5
+ required: false
6
+ } | {
7
+ required: true
8
+ roles?: Roles[]
9
+ }
10
+ }
11
+ }
12
+
13
+ // It is always important to ensure you import/export something when augmenting a type
14
+ export { }
15
+
package/package.json CHANGED
@@ -1,54 +1,56 @@
1
1
  {
2
- "name": "@meistrari/auth-nuxt",
3
- "version": "1.1.0",
4
- "type": "module",
5
- "exports": {
6
- ".": {
7
- "types": "./dist/types.d.mts",
8
- "import": "./dist/module.mjs"
2
+ "name": "@meistrari/auth-nuxt",
3
+ "version": "2.0.1",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/types.d.mts",
8
+ "import": "./dist/module.mjs"
9
+ },
10
+ "./server/middleware/auth": {
11
+ "types": "./dist/runtime/server/middleware/auth.d.ts",
12
+ "import": "./dist/runtime/server/middleware/auth.js"
13
+ },
14
+ "./core": {
15
+ "types": "./dist/core.d.mts",
16
+ "import": "./dist/core.mjs"
17
+ }
9
18
  },
10
- "./server/middleware/auth": {
11
- "types": "./dist/runtime/server/middleware/auth.d.ts",
12
- "import": "./dist/runtime/server/middleware/auth.js"
19
+ "main": "./dist/module.mjs",
20
+ "typesVersions": {
21
+ "*": {
22
+ ".": [
23
+ "./dist/types.d.mts"
24
+ ]
25
+ }
13
26
  },
14
- "./core": {
15
- "types": "./dist/core.d.mts",
16
- "import": "./dist/core.mjs"
17
- }
18
- },
19
- "main": "./dist/module.mjs",
20
- "typesVersions": {
21
- "*": {
22
- ".": [
23
- "./dist/types.d.mts"
24
- ]
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "scripts": {
31
+ "build": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt-module-build build"
32
+ },
33
+ "dependencies": {
34
+ "@meistrari/auth-core": "1.4.0",
35
+ "jose": "6.1.3"
36
+ },
37
+ "peerDependencies": {
38
+ "nuxt": "^3.0.0 || ^4.0.0",
39
+ "vue": "^3.0.0"
40
+ },
41
+ "devDependencies": {
42
+ "@nuxt/devtools": "2.6.3",
43
+ "@nuxt/eslint-config": "1.9.0",
44
+ "@nuxt/kit": "4.0.3",
45
+ "@nuxt/module-builder": "1.0.2",
46
+ "@nuxt/schema": "4.0.3",
47
+ "@nuxt/test-utils": "3.19.2",
48
+ "@types/node": "latest",
49
+ "changelogen": "0.6.2",
50
+ "nuxt": "4.0.3",
51
+ "typescript": "5.9.2",
52
+ "unbuild": "3.6.1",
53
+ "vitest": "3.2.4",
54
+ "vue-tsc": "3.0.6"
25
55
  }
26
- },
27
- "files": [
28
- "dist"
29
- ],
30
- "scripts": {
31
- "build": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt-module-build build"
32
- },
33
- "dependencies": {
34
- "@meistrari/auth-core": "1.6.0"
35
- },
36
- "peerDependencies": {
37
- "nuxt": "^3.0.0 || ^4.0.0"
38
- },
39
- "devDependencies": {
40
- "@nuxt/devtools": "2.6.3",
41
- "@nuxt/eslint-config": "1.9.0",
42
- "@nuxt/kit": "4.0.3",
43
- "@nuxt/module-builder": "1.0.2",
44
- "@nuxt/schema": "4.0.3",
45
- "@nuxt/test-utils": "3.19.2",
46
- "@types/node": "latest",
47
- "changelogen": "0.6.2",
48
- "nuxt": "4.0.3",
49
- "typescript": "5.9.2",
50
- "unbuild": "3.6.1",
51
- "vitest": "3.2.4",
52
- "vue-tsc": "3.0.6"
53
- }
54
56
  }