talon-auth-nuxt 0.13.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/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # talon-auth-nuxt
2
+
3
+ Nuxt module for integrating [Talon](https://talon.codes) authentication into your Nuxt application.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add talon-auth-nuxt talon-auth
9
+ ```
10
+
11
+ ## Setup
12
+
13
+ ### 1. Register the module
14
+
15
+ ```ts
16
+ // nuxt.config.ts
17
+ export default defineNuxtConfig({
18
+ modules: ['talon-auth-nuxt'],
19
+
20
+ talon: {
21
+ appId: process.env.TALON_APP_ID,
22
+ apiUrl: process.env.API_URL // defaults to https://api.talon.codes
23
+ }
24
+ })
25
+ ```
26
+
27
+ ### 2. Add the login component to your page
28
+
29
+ ```vue
30
+ <script setup lang="ts">
31
+ // Import the talon-auth login web component (client-side only)
32
+ if (import.meta.client) {
33
+ await import('talon-auth/login')
34
+ }
35
+
36
+ const config = useRuntimeConfig()
37
+ const { appId, apiUrl } = config.public.talon!
38
+ const user = useTalonUser()
39
+
40
+ onMounted(() => setupTalonLogin())
41
+ </script>
42
+
43
+ <template>
44
+ <talon-login :app-id="appId" :api-url="apiUrl" @login="onLogin" />
45
+
46
+ <div v-if="user">
47
+ Welcome, {{ user.email }}
48
+ <button @click="logout">Logout</button>
49
+ </div>
50
+ </template>
51
+ ```
52
+
53
+ ## Configuration
54
+
55
+ | Option | Type | Default | Description |
56
+ | -------------------- | -------- | ------------------------- | ------------------------------------- |
57
+ | `appId` | `string` | **required** | Your Talon application ID |
58
+ | `apiUrl` | `string` | `https://api.talon.codes` | Talon API URL |
59
+ | `authEndpoint` | `string` | `/api/auth` | Server endpoint for cookie management |
60
+ | `loginComponentName` | `string` | `talon-login` | Custom element tag name |
61
+ | `cookie` | `object` | see below | Cookie configuration |
62
+
63
+ ### Cookie Configuration
64
+
65
+ ```ts
66
+ cookie: {
67
+ name: 'talon_auth', // Cookie name
68
+ httpOnly: true, // HTTP-only cookie
69
+ secure: true, // Secure in production
70
+ sameSite: 'lax', // SameSite policy
71
+ maxAge: 60 * 60 * 24 * 7, // 7 days
72
+ path: '/' // Cookie path
73
+ }
74
+ ```
75
+
76
+ ## Composables
77
+
78
+ The module auto-imports the following composables:
79
+
80
+ ### `useTalonUser()`
81
+
82
+ Returns a readonly ref of the current authenticated user.
83
+
84
+ ```ts
85
+ const user = useTalonUser()
86
+ // user.value is TalonAuthUser | null
87
+ ```
88
+
89
+ ### `useTalonLogout()`
90
+
91
+ Returns a logout function that clears the session.
92
+
93
+ ```ts
94
+ const logout = useTalonLogout()
95
+ await logout()
96
+ ```
97
+
98
+ ### `setupTalonLogin()`
99
+
100
+ Call this in `onMounted` to initialize the login flow and sync authentication state.
101
+
102
+ ```ts
103
+ onMounted(() => setupTalonLogin())
104
+ ```
105
+
106
+ ### `getLoginEl()`
107
+
108
+ Returns the `<talon-login>` DOM element. Useful for accessing the component's methods directly.
109
+
110
+ ```ts
111
+ const loginEl = getLoginEl()
112
+ const token = await loginEl.getAccessToken()
113
+ ```
114
+
115
+ ## How It Works
116
+
117
+ 1. **Server-side**: On each request, the module's server plugin verifies the auth cookie and populates the user state for SSR.
118
+
119
+ 2. **Client-side**: When `setupTalonLogin()` is called, it syncs with the `<talon-login>` component and updates the server cookie.
120
+
121
+ 3. **Cookie management**: The module registers server handlers at the configured `authEndpoint`:
122
+ - `POST /api/auth` - Verifies token and sets the auth cookie
123
+ - `DELETE /api/auth` - Clears the auth cookie
124
+
125
+ ## TypeScript
126
+
127
+ The module exports types from `talon-auth`:
128
+
129
+ ```ts
130
+ import type { TalonAuthUser, TalonLoginComponent } from 'talon-auth-nuxt'
131
+ ```
@@ -0,0 +1,36 @@
1
+ import * as _nuxt_schema from '@nuxt/schema';
2
+ import { TalonAuthUser, KvStore, DeviceData } from 'talon-auth';
3
+ export { TalonAuthClient, TalonAuthClientInterface, TalonAuthUser } from 'talon-auth';
4
+ export { UseTalonClientOptions } from '../dist/runtime/composables/use-talon.js';
5
+
6
+ /**
7
+ * Context passed to callback functions for dynamic configuration.
8
+ * Available in both client and server environments.
9
+ */
10
+ interface TalonContext {
11
+ path: string;
12
+ user: TalonAuthUser | null;
13
+ }
14
+ type TalonStore = KvStore<DeviceData>;
15
+ type TalonStoreConfig = 'indexedDb' | 'memory';
16
+ interface TalonCookieOptions {
17
+ name?: string;
18
+ httpOnly?: boolean;
19
+ secure?: boolean;
20
+ sameSite?: 'lax' | 'strict' | 'none';
21
+ maxAge?: number;
22
+ path?: string;
23
+ }
24
+ interface TalonAuthModuleOptions {
25
+ appId: string;
26
+ apiUrl?: string;
27
+ authEndpoint?: string;
28
+ loginComponentName?: string;
29
+ cookie?: TalonCookieOptions;
30
+ store?: TalonStoreConfig;
31
+ loginExpiry?: number;
32
+ }
33
+ declare const _default: _nuxt_schema.NuxtModule<TalonAuthModuleOptions, TalonAuthModuleOptions, false>;
34
+
35
+ export { _default as default };
36
+ export type { TalonAuthModuleOptions, TalonContext, TalonCookieOptions, TalonStore, TalonStoreConfig };
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "talon-auth-nuxt",
3
+ "configKey": "talon",
4
+ "compatibility": {
5
+ "nuxt": ">=3.0.0"
6
+ },
7
+ "version": "0.13.1",
8
+ "builder": {
9
+ "@nuxt/module-builder": "1.0.2",
10
+ "unbuild": "3.6.1"
11
+ }
12
+ }
@@ -0,0 +1,84 @@
1
+ import { defineNuxtModule, createResolver, addImports, addPlugin, addServerHandler } from '@nuxt/kit';
2
+
3
+ const module$1 = defineNuxtModule({
4
+ meta: {
5
+ name: "talon-auth-nuxt",
6
+ configKey: "talon",
7
+ compatibility: {
8
+ nuxt: ">=3.0.0"
9
+ }
10
+ },
11
+ defaults: {
12
+ appId: "",
13
+ apiUrl: "https://api.talon.codes",
14
+ authEndpoint: "/api/auth",
15
+ loginComponentName: "talon-login",
16
+ cookie: {
17
+ name: "talon_auth",
18
+ httpOnly: true,
19
+ secure: process.env.NODE_ENV === "production",
20
+ sameSite: "lax",
21
+ // 'lax' allows cookie on navigation from external links; 'strict' would break auth
22
+ maxAge: void 0,
23
+ // defaults to token expiry if not set
24
+ path: "/"
25
+ },
26
+ store: "indexedDb",
27
+ loginExpiry: 60 * 15
28
+ // 15 minutes (in seconds)
29
+ },
30
+ setup(options, nuxt) {
31
+ const resolver = createResolver(import.meta.url);
32
+ if (!options.appId) {
33
+ console.warn("[talon-auth-nuxt] appId is required in talon config. Authentication will not work.");
34
+ }
35
+ nuxt.options.runtimeConfig.public.talon = {
36
+ appId: options.appId,
37
+ apiUrl: options.apiUrl,
38
+ loginComponentName: options.loginComponentName,
39
+ store: options.store,
40
+ loginExpiry: options.loginExpiry
41
+ };
42
+ nuxt.options.runtimeConfig.talon = {
43
+ authEndpoint: options.authEndpoint,
44
+ cookie: options.cookie
45
+ };
46
+ const composables = ["useTalonClient", "getTalonAuthToken", "useTalonUser", "useTalonLogout", "setupTalonLogin"];
47
+ for (const name of composables) {
48
+ addImports({
49
+ name,
50
+ as: name,
51
+ from: resolver.resolve("./runtime/composables/use-talon")
52
+ });
53
+ }
54
+ addPlugin({
55
+ src: resolver.resolve("./runtime/plugins/talon.server"),
56
+ mode: "server",
57
+ order: -50
58
+ });
59
+ const endpoint = options.authEndpoint || "/api/auth";
60
+ addServerHandler({
61
+ route: endpoint,
62
+ method: "post",
63
+ handler: resolver.resolve("./runtime/server/api/auth.post")
64
+ });
65
+ addServerHandler({
66
+ route: endpoint,
67
+ method: "delete",
68
+ handler: resolver.resolve("./runtime/server/api/auth.delete")
69
+ });
70
+ const componentName = options.loginComponentName || "talon-login";
71
+ nuxt.options.vue = nuxt.options.vue || {};
72
+ nuxt.options.vue.compilerOptions = nuxt.options.vue.compilerOptions || {};
73
+ const existingIsCustomElement = nuxt.options.vue.compilerOptions.isCustomElement;
74
+ nuxt.options.vue.compilerOptions.isCustomElement = (tag) => {
75
+ if (tag === componentName) return true;
76
+ if (typeof existingIsCustomElement === "function") {
77
+ return existingIsCustomElement(tag);
78
+ }
79
+ return false;
80
+ };
81
+ }
82
+ });
83
+
84
+ export { module$1 as default };
File without changes
@@ -0,0 +1,108 @@
1
+ import { createClient, createIndexedDbStore, createMemoryStore } from "talon-auth";
2
+ import { readonly, useCookie, useNuxtApp, useRoute, useState, useRuntimeConfig } from "#imports";
3
+ export function useTalonClient(options) {
4
+ if (import.meta.server) return null;
5
+ const nuxtApp = useNuxtApp();
6
+ const config = useRuntimeConfig();
7
+ const route = useRoute();
8
+ if (options?.store || options?.loginExpiry) {
9
+ const store2 = options.store ?? createStoreFromConfig(config.public.talon?.store);
10
+ const loginExpiry2 = resolveLoginExpiry(options.loginExpiry ?? config.public.talon?.loginExpiry, {
11
+ path: route.path,
12
+ user: null
13
+ // User not available yet when creating client
14
+ });
15
+ return createClient({
16
+ appId: config.public.talon?.appId,
17
+ api: config.public.talon?.apiUrl,
18
+ store: store2,
19
+ loginExpiry: loginExpiry2
20
+ });
21
+ }
22
+ if (nuxtApp._talonClient) {
23
+ return nuxtApp._talonClient;
24
+ }
25
+ const store = createStoreFromConfig(config.public.talon?.store);
26
+ const loginExpiry = resolveLoginExpiry(config.public.talon?.loginExpiry, {
27
+ path: route.path,
28
+ user: null
29
+ });
30
+ nuxtApp._talonClient = createClient({
31
+ appId: config.public.talon?.appId,
32
+ api: config.public.talon?.apiUrl,
33
+ store,
34
+ loginExpiry
35
+ });
36
+ return nuxtApp._talonClient;
37
+ }
38
+ function createStoreFromConfig(storeConfig) {
39
+ if (storeConfig === "memory") {
40
+ return createMemoryStore();
41
+ }
42
+ return createIndexedDbStore();
43
+ }
44
+ function resolveLoginExpiry(config, ctx) {
45
+ if (typeof config === "function") {
46
+ return config(ctx);
47
+ }
48
+ return config ?? 60 * 15;
49
+ }
50
+ export function getTalonAuthToken() {
51
+ const config = useRuntimeConfig();
52
+ const cookieName = config.talon?.cookie?.name || "talon_auth";
53
+ if (import.meta.server) {
54
+ const authCookie = useCookie(cookieName);
55
+ return async () => authCookie.value ?? null;
56
+ }
57
+ const client = useTalonClient();
58
+ return async () => {
59
+ try {
60
+ return await client?.getAccessToken() ?? null;
61
+ } catch {
62
+ return null;
63
+ }
64
+ };
65
+ }
66
+ export function useTalonUser() {
67
+ const user = useState("talon-user", () => null);
68
+ return readonly(user);
69
+ }
70
+ export function useTalonLogout() {
71
+ const user = useState("talon-user");
72
+ const config = useRuntimeConfig();
73
+ const authEndpoint = config.public.talon?.authEndpoint || "/api/auth";
74
+ const client = useTalonClient();
75
+ return async function logout() {
76
+ if (import.meta.server) {
77
+ return;
78
+ }
79
+ await client?.logout();
80
+ user.value = null;
81
+ await $fetch(authEndpoint, { method: "DELETE" });
82
+ };
83
+ }
84
+ export async function setupTalonLogin() {
85
+ const user = useState("talon-user");
86
+ const config = useRuntimeConfig();
87
+ const authEndpoint = config.public.talon?.authEndpoint || "/api/auth";
88
+ const client = useTalonClient();
89
+ async function updateCookie(token) {
90
+ try {
91
+ await $fetch(authEndpoint, {
92
+ method: "POST",
93
+ body: { token }
94
+ });
95
+ } catch {
96
+ user.value = null;
97
+ }
98
+ }
99
+ if (client) {
100
+ try {
101
+ const accessToken = await client.getAccessToken();
102
+ await updateCookie(accessToken);
103
+ user.value = await client.getUser();
104
+ } catch {
105
+ user.value = null;
106
+ }
107
+ }
108
+ }
File without changes
@@ -0,0 +1,24 @@
1
+ import { createVerifier } from "talon-auth";
2
+ import { defineNuxtPlugin, useCookie, useRuntimeConfig, useState } from "#imports";
3
+ export default defineNuxtPlugin(async () => {
4
+ const config = useRuntimeConfig();
5
+ const appId = config.public.talon?.appId;
6
+ if (!appId) {
7
+ console.warn("talon-auth-nuxt: appId is not configured in nuxt.config");
8
+ return;
9
+ }
10
+ const cookieName = config.talon?.cookie?.name || "talon_auth";
11
+ const user = useState("talon-user", () => null);
12
+ const accessToken = useCookie(cookieName);
13
+ if (accessToken.value) {
14
+ try {
15
+ const verifier = createVerifier({ appId });
16
+ const verified = await verifier.verify(accessToken.value);
17
+ if (verified.user) {
18
+ user.value = verified.user;
19
+ }
20
+ } catch {
21
+ accessToken.value = null;
22
+ }
23
+ }
24
+ });
File without changes
@@ -0,0 +1,9 @@
1
+ import { defineEventHandler, deleteCookie, useRuntimeConfig } from "#imports";
2
+ export default defineEventHandler(async (event) => {
3
+ const config = useRuntimeConfig();
4
+ const cookieName = config.talon?.cookie?.name || "talon_auth";
5
+ deleteCookie(event, cookieName);
6
+ return {
7
+ success: true
8
+ };
9
+ });
File without changes
@@ -0,0 +1,46 @@
1
+ import { createVerifier } from "talon-auth";
2
+ import { createError, defineEventHandler, readBody, setCookie, useRuntimeConfig } from "#imports";
3
+ export default defineEventHandler(async (event) => {
4
+ const config = useRuntimeConfig();
5
+ const appId = config.public.talon?.appId;
6
+ if (!appId) {
7
+ throw createError({
8
+ statusCode: 500,
9
+ message: "talon-auth-nuxt: appId is not configured in nuxt.config"
10
+ });
11
+ }
12
+ const body = await readBody(event);
13
+ if (!body?.token) {
14
+ throw createError({
15
+ statusCode: 400,
16
+ message: "Access token is required"
17
+ });
18
+ }
19
+ try {
20
+ const verifier = createVerifier({ appId });
21
+ const verified = await verifier.verify(body.token);
22
+ if (!verified.user) {
23
+ throw createError({
24
+ statusCode: 401,
25
+ message: "Invalid access token"
26
+ });
27
+ }
28
+ const { name, maxAge: configuredMaxAge, ...cookieOptions } = config.talon.cookie;
29
+ let maxAge = configuredMaxAge;
30
+ if (!maxAge && verified.payload.exp) {
31
+ const now = Math.floor(Date.now() / 1e3);
32
+ maxAge = verified.payload.exp - now;
33
+ }
34
+ const finalOptions = maxAge && maxAge > 0 ? { ...cookieOptions, maxAge } : cookieOptions;
35
+ setCookie(event, name, body.token, finalOptions);
36
+ return {
37
+ success: true,
38
+ user: verified.user
39
+ };
40
+ } catch {
41
+ throw createError({
42
+ statusCode: 401,
43
+ message: "Access token verification failed"
44
+ });
45
+ }
46
+ });
@@ -0,0 +1,36 @@
1
+ declare module '#imports' {
2
+ export const readonly: (typeof import('vue'))['readonly']
3
+ export const useState: (typeof import('nuxt/app'))['useState']
4
+ export const useNuxtApp: (typeof import('nuxt/app'))['useNuxtApp']
5
+ export const useRoute: (typeof import('nuxt/app'))['useRoute']
6
+ export const useRuntimeConfig: () => {
7
+ public: {
8
+ talon?: {
9
+ appId?: string
10
+ apiUrl?: string
11
+ loginComponentName?: string
12
+ authEndpoint?: string
13
+ store?: 'indexedDb' | 'memory'
14
+ loginExpiry?: number
15
+ }
16
+ }
17
+ talon?: {
18
+ authEndpoint?: string
19
+ cookie?: {
20
+ name?: string
21
+ httpOnly?: boolean
22
+ secure?: boolean
23
+ sameSite?: 'lax' | 'strict' | 'none'
24
+ maxAge?: number
25
+ path?: string
26
+ }
27
+ }
28
+ }
29
+ export const useCookie: (typeof import('nuxt/app'))['useCookie']
30
+ export const defineNuxtPlugin: (typeof import('nuxt/app'))['defineNuxtPlugin']
31
+ export const createError: (typeof import('h3'))['createError']
32
+ export const defineEventHandler: (typeof import('h3'))['defineEventHandler']
33
+ export const readBody: (typeof import('h3'))['readBody']
34
+ export const setCookie: (typeof import('h3'))['setCookie']
35
+ export const deleteCookie: (typeof import('h3'))['deleteCookie']
36
+ }
@@ -0,0 +1,13 @@
1
+ import type { NuxtModule } from '@nuxt/schema'
2
+
3
+ import type { default as Module } from './module.mjs'
4
+
5
+ export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
6
+
7
+ export { type TalonAuthClient, type TalonAuthClientInterface, type TalonAuthUser } from 'talon-auth'
8
+
9
+ export { type UseTalonClientOptions } from '../dist/runtime/composables/use-talon.js'
10
+
11
+ export { default } from './module.mjs'
12
+
13
+ export { type TalonAuthModuleOptions, type TalonContext, type TalonCookieOptions, type TalonStore, type TalonStoreConfig } from './module.mjs'
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "talon-auth-nuxt",
3
+ "version": "0.13.1",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/types.d.mts",
9
+ "import": "./dist/module.mjs"
10
+ }
11
+ },
12
+ "main": "./dist/module.mjs",
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "scripts": {
17
+ "build": "nuxt-module-build build",
18
+ "dev": "nuxt-module-build build --stub",
19
+ "prepublish": "pnpm build",
20
+ "test": "vitest --run"
21
+ },
22
+ "dependencies": {
23
+ "@nuxt/kit": "^4.3.1",
24
+ "talon-auth": "^0.13.1"
25
+ },
26
+ "devDependencies": {
27
+ "@nuxt/module-builder": "^1.0.2",
28
+ "happy-dom": "^20.6.3",
29
+ "nuxt": "^4.3.1",
30
+ "vitest": "^3.2.4"
31
+ },
32
+ "gitHead": "05917231b2ce1b808f6955cf082ef8588252ecf3"
33
+ }