@habityzer/nuxt-symfony-kinde-layer 2.1.2 → 2.2.0

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/.env.example ADDED
@@ -0,0 +1,17 @@
1
+ # Kinde Authentication Configuration
2
+ # These are required for the @habityzer/nuxt-kinde-auth module
3
+ KINDE_AUTH_DOMAIN=https://your-domain.kinde.com
4
+ KINDE_CLIENT_ID=your_client_id
5
+ KINDE_CLIENT_SECRET=your_client_secret
6
+ KINDE_REDIRECT_URL=http://localhost:3000/api/auth/callback
7
+ KINDE_LOGOUT_REDIRECT_URL=http://localhost:3000
8
+
9
+ # Auth Cookie and Middleware Configuration
10
+ NUXT_PUBLIC_AUTH_COOKIE_PREFIX=auth_
11
+ NUXT_PUBLIC_AUTH_LOGIN_PATH=/login
12
+ NUXT_PUBLIC_AUTH_CLOCK_SKEW_SECONDS=300
13
+ NUXT_PUBLIC_AUTH_APP_TOKEN_PREFIX=Bearer
14
+ NUXT_PUBLIC_AUTH_E2E_TOKEN_COOKIE_NAME=e2e_token
15
+ NUXT_PUBLIC_AUTH_ID_TOKEN_NAME=id_token
16
+ NUXT_PUBLIC_AUTH_ACCESS_TOKEN_NAME=access_token
17
+ NUXT_PUBLIC_AUTH_REFRESH_TOKEN_NAME=refresh_token
@@ -0,0 +1,66 @@
1
+ name: Publish to npm
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ - master
8
+
9
+ jobs:
10
+ release:
11
+ runs-on: ubuntu-latest
12
+ permissions:
13
+ contents: write
14
+ issues: write
15
+ pull-requests: write
16
+ id-token: write
17
+
18
+ steps:
19
+ - name: Checkout code
20
+ uses: actions/checkout@v4
21
+ with:
22
+ fetch-depth: 0
23
+ persist-credentials: false
24
+
25
+ - name: Setup pnpm
26
+ uses: pnpm/action-setup@v4
27
+ with:
28
+ version: 9
29
+
30
+ - name: Setup Node.js
31
+ uses: actions/setup-node@v4
32
+ with:
33
+ node-version: '20'
34
+ cache: 'pnpm'
35
+ registry-url: 'https://registry.npmjs.org'
36
+
37
+ - name: Install dependencies
38
+ run: pnpm install --frozen-lockfile
39
+
40
+ - name: Prepare Nuxt
41
+ env:
42
+ KINDE_AUTH_DOMAIN: https://placeholder.kinde.com
43
+ KINDE_CLIENT_ID: placeholder_client_id
44
+ KINDE_CLIENT_SECRET: placeholder_client_secret
45
+ KINDE_REDIRECT_URL: http://localhost:3000/api/auth/callback
46
+ KINDE_LOGOUT_REDIRECT_URL: http://localhost:3000
47
+ NUXT_PUBLIC_AUTH_COOKIE_PREFIX: auth_
48
+ NUXT_PUBLIC_AUTH_LOGIN_PATH: /login
49
+ NUXT_PUBLIC_AUTH_CLOCK_SKEW_SECONDS: 300
50
+ NUXT_PUBLIC_AUTH_APP_TOKEN_PREFIX: Bearer
51
+ NUXT_PUBLIC_AUTH_E2E_TOKEN_COOKIE_NAME: e2e_token
52
+ NUXT_PUBLIC_AUTH_ID_TOKEN_NAME: id_token
53
+ NUXT_PUBLIC_AUTH_ACCESS_TOKEN_NAME: access_token
54
+ NUXT_PUBLIC_AUTH_REFRESH_TOKEN_NAME: refresh_token
55
+ run: pnpm nuxt prepare
56
+
57
+ - name: Run linter
58
+ run: pnpm lint
59
+
60
+ - name: Release
61
+ env:
62
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
63
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
64
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
65
+ run: pnpm release
66
+
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env sh
2
+ . "$(dirname -- "$0")/_/husky.sh"
3
+
4
+ # Validate commit message format
5
+ pnpm exec commitlint --edit "$1"
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # Pre-commit hook to ensure code quality
4
+ set -e
5
+
6
+ echo "🔍 Running pre-commit checks..."
7
+
8
+ # Check if .nuxt directory exists, if not, prepare it
9
+ if [ ! -d ".nuxt" ]; then
10
+ echo "📦 Preparing Nuxt (first time)..."
11
+ export KINDE_AUTH_DOMAIN="https://placeholder.kinde.com"
12
+ export KINDE_CLIENT_ID="placeholder_client_id"
13
+ export KINDE_CLIENT_SECRET="placeholder_client_secret"
14
+ export KINDE_REDIRECT_URL="http://localhost:3000/api/auth/callback"
15
+ export KINDE_LOGOUT_REDIRECT_URL="http://localhost:3000"
16
+ export NUXT_PUBLIC_AUTH_COOKIE_PREFIX="auth_"
17
+ export NUXT_PUBLIC_AUTH_LOGIN_PATH="/login"
18
+ export NUXT_PUBLIC_AUTH_CLOCK_SKEW_SECONDS="300"
19
+ export NUXT_PUBLIC_AUTH_APP_TOKEN_PREFIX="Bearer"
20
+ export NUXT_PUBLIC_AUTH_E2E_TOKEN_COOKIE_NAME="e2e_token"
21
+ export NUXT_PUBLIC_AUTH_ID_TOKEN_NAME="id_token"
22
+ export NUXT_PUBLIC_AUTH_ACCESS_TOKEN_NAME="access_token"
23
+ export NUXT_PUBLIC_AUTH_REFRESH_TOKEN_NAME="refresh_token"
24
+
25
+ pnpm nuxt prepare > /dev/null 2>&1
26
+ fi
27
+
28
+ # Run linter
29
+ echo "✨ Running ESLint..."
30
+ pnpm lint
31
+
32
+ echo "✅ Pre-commit checks passed!"
package/.releaserc.json CHANGED
@@ -23,7 +23,7 @@
23
23
  "changelogFile": "CHANGELOG.md"
24
24
  }],
25
25
  ["@semantic-release/npm", {
26
- "npmPublish": false
26
+ "npmPublish": true
27
27
  }],
28
28
  ["@semantic-release/git", {
29
29
  "assets": ["package.json", "CHANGELOG.md"],
package/CHANGELOG.md CHANGED
@@ -1,3 +1,26 @@
1
+ # [2.2.0](https://github.com/Habityzer/nuxt-symfony-kinde-layer/compare/v2.1.4...v2.2.0) (2026-02-13)
2
+
3
+
4
+ ### Features
5
+
6
+ * Enhance Kinde authentication configuration and middleware handling ([d8b722a](https://github.com/Habityzer/nuxt-symfony-kinde-layer/commit/d8b722a8ba5a126f02c31aea0347fa3e21bb29b9))
7
+ * Refactor Kinde authentication configuration and enhance middleware setup ([c577a2a](https://github.com/Habityzer/nuxt-symfony-kinde-layer/commit/c577a2a047299f30cb745a91aac82b50c2bfedb1))
8
+
9
+ ## [2.1.4](https://github.com/Habityzer/nuxt-symfony-kinde-layer/compare/v2.1.3...v2.1.4) (2026-01-01)
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * Add Nuxt prepare step to CI and correct ESLint config import path ([07471f5](https://github.com/Habityzer/nuxt-symfony-kinde-layer/commit/07471f5234e1322a2ccfe5709cb8109badededda))
15
+ * Remove invalid pnpm-workspace.yaml for single package ([7e7d6a1](https://github.com/Habityzer/nuxt-symfony-kinde-layer/commit/7e7d6a19ffd2edccfee80b944c947c364b6b2863))
16
+
17
+ ## [2.1.3](https://github.com/Habityzer/nuxt-symfony-kinde-layer/compare/v2.1.2...v2.1.3) (2026-01-01)
18
+
19
+
20
+ ### Bug Fixes
21
+
22
+ * Update Kinde configuration to use environment variables and enhance Symfony proxy request handling ([3e0d0bd](https://github.com/Habityzer/nuxt-symfony-kinde-layer/commit/3e0d0bd09ea26a21940f0a0d16cc9aaf5a327be8))
23
+
1
24
  ## [2.1.2](https://github.com/Habityzer/nuxt-symfony-kinde-layer/compare/v2.1.1...v2.1.2) (2025-12-26)
2
25
 
3
26
 
package/README.md CHANGED
@@ -257,6 +257,83 @@ Add these scripts to your project's `package.json`:
257
257
  }
258
258
  ```
259
259
 
260
+ ## Development
261
+
262
+ ### Local Development Setup
263
+
264
+ 1. **Install dependencies:**
265
+ ```bash
266
+ pnpm install
267
+ ```
268
+
269
+ 2. **The project uses Husky for git hooks:**
270
+ - Pre-commit: Automatically runs `pnpm lint` before each commit
271
+ - Commit-msg: Validates commit message format (conventional commits)
272
+
273
+ 3. **Run linter manually:**
274
+ ```bash
275
+ pnpm lint # Check for issues
276
+ pnpm lint:fix # Auto-fix issues
277
+ ```
278
+
279
+ 4. **First time setup:**
280
+ The pre-commit hook will automatically run `nuxt prepare` if needed (with placeholder environment variables).
281
+
282
+ ### Commit Message Format
283
+
284
+ This project uses [Conventional Commits](https://www.conventionalcommits.org/). Your commits must follow this format:
285
+
286
+ ```
287
+ type(scope): subject
288
+
289
+ body (optional)
290
+ ```
291
+
292
+ **Types:**
293
+ - `feat`: New feature
294
+ - `fix`: Bug fix
295
+ - `docs`: Documentation changes
296
+ - `style`: Code style changes (formatting, etc.)
297
+ - `refactor`: Code refactoring
298
+ - `test`: Adding or updating tests
299
+ - `chore`: Maintenance tasks
300
+
301
+ **Examples:**
302
+ ```bash
303
+ git commit -m "feat: add authentication middleware"
304
+ git commit -m "fix: resolve cookie prefix conflict"
305
+ git commit -m "docs: update README with CI setup"
306
+ ```
307
+
308
+ ## CI/CD Setup
309
+
310
+ ### Required Environment Variables for GitHub Actions
311
+
312
+ When building or publishing this layer in CI/CD (e.g., GitHub Actions), you need to provide placeholder environment variables for the `nuxt prepare` step. The layer's `nuxt.config.ts` validates these at build time.
313
+
314
+ Add these to your workflow:
315
+
316
+ ```yaml
317
+ - name: Prepare Nuxt
318
+ env:
319
+ KINDE_AUTH_DOMAIN: https://placeholder.kinde.com
320
+ KINDE_CLIENT_ID: placeholder_client_id
321
+ KINDE_CLIENT_SECRET: placeholder_client_secret
322
+ KINDE_REDIRECT_URL: http://localhost:3000/api/auth/callback
323
+ KINDE_LOGOUT_REDIRECT_URL: http://localhost:3000
324
+ NUXT_PUBLIC_AUTH_COOKIE_PREFIX: auth_
325
+ NUXT_PUBLIC_AUTH_LOGIN_PATH: /login
326
+ NUXT_PUBLIC_AUTH_CLOCK_SKEW_SECONDS: 300
327
+ NUXT_PUBLIC_AUTH_APP_TOKEN_PREFIX: Bearer
328
+ NUXT_PUBLIC_AUTH_E2E_TOKEN_COOKIE_NAME: e2e_token
329
+ NUXT_PUBLIC_AUTH_ID_TOKEN_NAME: id_token
330
+ NUXT_PUBLIC_AUTH_ACCESS_TOKEN_NAME: access_token
331
+ NUXT_PUBLIC_AUTH_REFRESH_TOKEN_NAME: refresh_token
332
+ run: pnpm nuxt prepare
333
+ ```
334
+
335
+ **Note**: These are placeholder values only used for type generation and validation. Projects consuming this layer will provide their own real credentials at runtime.
336
+
260
337
  ## Troubleshooting
261
338
 
262
339
  ### Cookie Name Conflicts
@@ -324,3 +401,4 @@ MIT
324
401
  ## Contributing
325
402
 
326
403
  This is a private layer for Habityzer projects. For issues or improvements, contact the team.
404
+
@@ -0,0 +1,134 @@
1
+ # Setting Up Automated NPM Publishing
2
+
3
+ This project uses GitHub Actions with semantic-release to automatically publish to npm when you push to the `master` branch.
4
+
5
+ ## Setup Instructions
6
+
7
+ ### 1. Create NPM Access Token
8
+
9
+ > **Important**: As of December 9, 2025, npm classic tokens have been permanently revoked. You must use **granular access tokens** for CI/CD workflows.
10
+
11
+ **Option A: Using npm CLI (recommended):**
12
+ ```bash
13
+ npm token create --type automation --scope @habityzer
14
+ ```
15
+
16
+ **Option B: Using the web interface:**
17
+ 1. Go to [npmjs.com/settings/~/tokens](https://www.npmjs.com/settings/~/tokens) and log in
18
+ 2. Click **Generate New Token** → **Granular Access Token**
19
+ 3. Configure your token:
20
+ - **Token name**: `github-actions-publish` (or any descriptive name)
21
+ - **Expiration**: Up to 90 days (maximum for publish tokens)
22
+ - **Packages and scopes**: Select your package or `@habityzer` scope
23
+ - **Permissions**: Select **Read and write**
24
+ - ✅ **Enable "Bypass 2FA"** for automated workflows
25
+ 4. Click **Generate Token**
26
+ 5. Copy the token (starts with `npm_...`)
27
+
28
+ > **Note**: Granular tokens for publishing expire after a maximum of 90 days. Set a reminder to regenerate the token before expiration, or consider using [OIDC trusted publishing](https://docs.npmjs.com/generating-provenance-statements) for a more secure, token-free approach.
29
+
30
+ ### 2. Add NPM Token to GitHub
31
+
32
+ 1. Go to your GitHub repository
33
+ 2. Navigate to **Settings** → **Secrets and variables** → **Actions**
34
+ 3. Click **New repository secret**
35
+ 4. Name: `NPM_TOKEN`
36
+ 5. Value: Paste your npm token from step 1
37
+ 6. Click **Add secret**
38
+
39
+ ### 3. Configure GitHub Actions in NPM
40
+
41
+ When setting up your package on npmjs.com for automated publishing:
42
+
43
+ 1. Go to your package page on npmjs.com
44
+ 2. Navigate to **Settings** → **Publishing Access**
45
+ 3. Under **GitHub Actions**, you'll be asked for:
46
+ - **Workflow filename**: Enter `publish.yml`
47
+ - This tells npm which workflow file in `.github/workflows/` will publish your package
48
+
49
+ ### 4. How It Works
50
+
51
+ The workflow (`.github/workflows/publish.yml`) will:
52
+
53
+ 1. **Trigger**: Automatically runs when you push to the `master` branch
54
+ 2. **Analyze**: Semantic-release reads your commit messages (following conventional commits)
55
+ 3. **Version**: Automatically bumps the version based on your commits:
56
+ - `feat:` → minor version (1.0.0 → 1.1.0)
57
+ - `fix:` → patch version (1.0.0 → 1.0.1)
58
+ - `BREAKING CHANGE:` → major version (1.0.0 → 2.0.0)
59
+ 4. **Changelog**: Updates `CHANGELOG.md`
60
+ 5. **Git Tag**: Creates a git tag for the new version
61
+ 6. **Publish**: Publishes to npm
62
+ 7. **Commit**: Commits the changelog and version bump back to your repo
63
+
64
+ ### 5. Commit Message Format
65
+
66
+ Use conventional commits for automatic versioning:
67
+
68
+ ```bash
69
+ # Patch release (1.0.0 → 1.0.1)
70
+ git commit -m "fix: resolve authentication bug"
71
+
72
+ # Minor release (1.0.0 → 1.1.0)
73
+ git commit -m "feat: add new login method"
74
+
75
+ # Major release (1.0.0 → 2.0.0)
76
+ git commit -m "feat: redesign API
77
+
78
+ BREAKING CHANGE: API structure has changed"
79
+
80
+ # No release (documentation, etc.)
81
+ git commit -m "docs: update README"
82
+ git commit -m "chore: update dependencies"
83
+ ```
84
+
85
+ ### 6. Manual Release (Optional)
86
+
87
+ If you prefer to release manually:
88
+
89
+ ```bash
90
+ pnpm release
91
+ ```
92
+
93
+ This runs semantic-release locally. Make sure you have:
94
+ - `NPM_TOKEN` environment variable set
95
+ - Committed all your changes
96
+ - Pushed to the master branch
97
+
98
+ ## Troubleshooting
99
+
100
+ ### "No NPM_TOKEN found"
101
+ - Make sure you've added `NPM_TOKEN` as a GitHub secret
102
+ - Check that the secret name is exactly `NPM_TOKEN` (case-sensitive)
103
+
104
+ ### "No release published"
105
+ - Check your commit messages follow conventional commits format
106
+ - Semantic-release only publishes if there are releasable commits
107
+ - View the GitHub Actions logs for details
108
+
109
+ ### "Permission denied" or "Invalid npm token"
110
+ - Ensure you're using a **granular access token** (not classic token)
111
+ - Check that the token has **Read and write** permissions
112
+ - Ensure **"Bypass 2FA"** is enabled for CI/CD workflows
113
+ - Check that your npm account has publish access to the `@habityzer` scope
114
+ - Verify the token hasn't expired (granular tokens expire after max 90 days)
115
+
116
+ ### "Package already published"
117
+ - Semantic-release automatically handles versions
118
+ - If you manually published the same version, semantic-release will skip it
119
+
120
+ ## Current Configuration
121
+
122
+ - **Branch**: `master` (configured in `.releaserc.json`)
123
+ - **Package**: `@habityzer/nuxt-symfony-kinde-layer`
124
+ - **Workflow**: `.github/workflows/publish.yml`
125
+ - **NPM Publish**: Enabled (set in `.releaserc.json`)
126
+
127
+ ## Additional Resources
128
+
129
+ - [npm Granular Access Tokens Documentation](https://docs.npmjs.com/about-access-tokens#granular-access-tokens)
130
+ - [GitHub Blog: npm Classic Tokens Revoked (Dec 2025)](https://github.blog/changelog/2025-12-09-npm-classic-tokens-revoked-session-based-auth-and-cli-token-management-now-available/)
131
+ - [Semantic Release Documentation](https://semantic-release.gitbook.io/)
132
+ - [Conventional Commits](https://www.conventionalcommits.org/)
133
+ - [OIDC Trusted Publishing for npm](https://docs.npmjs.com/generating-provenance-statements)
134
+
@@ -1,6 +1,8 @@
1
1
  import { computed, ref, readonly } from 'vue'
2
2
  import { E2E_TOKEN_COOKIE_NAME } from '../constants/auth'
3
3
 
4
+ const LEGACY_E2E_STORAGE_KEY = 'e2e_app_token'
5
+
4
6
  interface SymfonyUser {
5
7
  id: number
6
8
  email: string
@@ -105,9 +107,30 @@ export const useAuth = () => {
105
107
  // Clear local Symfony state
106
108
  userProfile.value = null
107
109
 
108
- // Clear E2E test token if exists
110
+ // Clear auth cookies first so route middleware blocks protected pages immediately.
109
111
  if (import.meta.client) {
110
- localStorage.removeItem('e2e_app_token')
112
+ const config = useRuntimeConfig()
113
+ const kindeConfig = config.public.kindeAuth || {}
114
+ const cookieConfig = kindeConfig.cookie || {}
115
+ const middlewareConfig = kindeConfig.middleware || {}
116
+ const cookiePrefix = requireString(cookieConfig.prefix, 'kindeAuth.cookie.prefix')
117
+ const idTokenName = requireString(cookieConfig.idTokenName, 'kindeAuth.cookie.idTokenName')
118
+ const accessTokenName = requireString(cookieConfig.accessTokenName, 'kindeAuth.cookie.accessTokenName')
119
+ const refreshTokenName = requireString(cookieConfig.refreshTokenName, 'kindeAuth.cookie.refreshTokenName')
120
+ const e2eTokenCookieName = requireString(middlewareConfig.e2eTokenCookieName, 'kindeAuth.middleware.e2eTokenCookieName')
121
+
122
+ const authCookies = [idTokenName, accessTokenName, refreshTokenName]
123
+ const scopedE2eCookieName = `${cookiePrefix}${e2eTokenCookieName}`
124
+ const scopedE2eStorageKey = `${cookiePrefix}e2e_app_token`
125
+
126
+ authCookies.forEach((cookieName) => {
127
+ document.cookie = `${cookiePrefix}${cookieName}=; path=/; max-age=0`
128
+ })
129
+
130
+ // Remove both scoped and legacy keys for backward compatibility.
131
+ localStorage.removeItem(scopedE2eStorageKey)
132
+ localStorage.removeItem(LEGACY_E2E_STORAGE_KEY)
133
+ document.cookie = `${scopedE2eCookieName}=; path=/; max-age=0`
111
134
  document.cookie = `${E2E_TOKEN_COOKIE_NAME}=; path=/; max-age=0`
112
135
  }
113
136
 
@@ -137,3 +160,11 @@ export const useAuth = () => {
137
160
  fetchUserProfile
138
161
  }
139
162
  }
163
+
164
+ function requireString(value: unknown, key: string): string {
165
+ if (typeof value === 'string' && value.trim().length > 0) {
166
+ return value
167
+ }
168
+
169
+ throw new Error(`[useAuth] Missing required config: ${key}`)
170
+ }
@@ -1,26 +1,9 @@
1
- /**
2
- * Authentication constants shared across the application
3
- */
4
-
5
- /**
6
- * Cookie name for E2E test authentication token
7
- * Used by automated tests to bypass Kinde OAuth flow
8
- */
9
- export const E2E_TOKEN_COOKIE_NAME = 'kinde_token'
10
-
11
- /**
12
- * Prefix for Symfony app tokens (used in E2E tests)
13
- * These are long-lived tokens generated by `php bin/console app:token:manage create`
14
- */
15
- export const APP_TOKEN_PREFIX = 'app_'
16
-
17
- /**
18
- * Kinde authentication cookie names
19
- * These cookies are managed by the @habityzer/nuxt-kinde-auth module
20
- * The prefix is configured per-project in nuxt.config.ts
21
- *
22
- * Note: These constants use placeholder names. The actual cookie names
23
- * will have the project-specific prefix (e.g., 'ew-id_token', 'habityzer_id_token')
24
- */
25
- export const KINDE_ID_TOKEN_COOKIE_NAME = 'id_token'
26
- export const KINDE_ACCESS_TOKEN_COOKIE_NAME = 'access_token'
1
+ export {
2
+ APP_TOKEN_PREFIX,
3
+ CLOCK_SKEW_SECONDS,
4
+ DEFAULT_LOGIN_PATH,
5
+ E2E_TOKEN_COOKIE_NAME,
6
+ KINDE_ACCESS_TOKEN_COOKIE_NAME,
7
+ KINDE_ID_TOKEN_COOKIE_NAME,
8
+ KINDE_REFRESH_TOKEN_COOKIE_NAME
9
+ } from '../../shared/auth-constants'
@@ -0,0 +1,134 @@
1
+ export default defineNuxtPlugin(() => {
2
+ const router = useRouter()
3
+ const config = useRuntimeConfig()
4
+ const kindeConfig = config.public.kindeAuth || {}
5
+ const middlewareConfig = kindeConfig.middleware || {}
6
+ const cookieConfig = kindeConfig.cookie || {}
7
+ // @ts-expect-error - cookie property exists in runtime config but not in module types
8
+ const cookiePrefix = requireString(cookieConfig.prefix, 'kindeAuth.cookie.prefix')
9
+ const idTokenBaseName = requireString(cookieConfig.idTokenName, 'kindeAuth.cookie.idTokenName')
10
+ const accessTokenBaseName = requireString(cookieConfig.accessTokenName, 'kindeAuth.cookie.accessTokenName')
11
+ const e2eTokenCookieName = requireString(middlewareConfig.e2eTokenCookieName, 'kindeAuth.middleware.e2eTokenCookieName')
12
+ const appTokenPrefix = requireString(middlewareConfig.appTokenPrefix, 'kindeAuth.middleware.appTokenPrefix')
13
+ const clockSkewSeconds = requireNonNegativeNumber(middlewareConfig.clockSkewSeconds, 'kindeAuth.middleware.clockSkewSeconds')
14
+ const idToken = useCookie<string | null>(`${cookiePrefix}${idTokenBaseName}`)
15
+ const accessToken = useCookie<string | null>(`${cookiePrefix}${accessTokenBaseName}`)
16
+ const e2eToken = useCookie<string | null>(`${cookiePrefix}${e2eTokenCookieName}`)
17
+ const publicRoutes: string[] = middlewareConfig.publicRoutes || ['/']
18
+ const loginPath = requireString(middlewareConfig.loginPath, 'kindeAuth.middleware.loginPath')
19
+
20
+ router.beforeEach((to) => {
21
+ if (to.path.startsWith('/api') || to.path.startsWith('/_nuxt')) {
22
+ return true
23
+ }
24
+
25
+ const isPublicRoute = publicRoutes.some(route => to.path === route || to.path.startsWith(`${route}/`))
26
+ logClient('route-check', { path: to.path, isPublicRoute })
27
+ if (isPublicRoute) {
28
+ return true
29
+ }
30
+
31
+ const hasIdToken = !!idToken.value
32
+ const hasAccessToken = !!accessToken.value
33
+ const e2eTokenValue = e2eToken.value
34
+
35
+ logClient('cookie-state', {
36
+ path: to.path,
37
+ cookiePrefix,
38
+ hasIdToken,
39
+ hasAccessToken,
40
+ hasScopedE2eToken: !!e2eTokenValue
41
+ })
42
+
43
+ if (e2eTokenValue && e2eTokenValue.startsWith(appTokenPrefix)) {
44
+ logClient('allow-e2e-app-token', { path: to.path })
45
+ return true
46
+ }
47
+
48
+ if (!hasIdToken && !hasAccessToken) {
49
+ logClient('redirect-missing-auth-cookies', { path: to.path })
50
+ window.location.href = loginPath
51
+ return false
52
+ }
53
+
54
+ const idTokenUsable = hasIdToken ? isUsableToken(idToken.value as string, appTokenPrefix, clockSkewSeconds) : false
55
+ const accessTokenUsable = hasAccessToken ? isUsableToken(accessToken.value as string, appTokenPrefix, clockSkewSeconds) : false
56
+ const isUnauthorized = !idTokenUsable && !accessTokenUsable
57
+
58
+ logClient('token-evaluation', {
59
+ path: to.path,
60
+ idTokenUsable,
61
+ accessTokenUsable
62
+ })
63
+
64
+ if (isUnauthorized) {
65
+ idToken.value = null
66
+ accessToken.value = null
67
+ logClient('redirect-all-auth-tokens-invalid-or-expired', { path: to.path })
68
+ window.location.href = loginPath
69
+ return false
70
+ }
71
+
72
+ logClient('allow-protected-route', { path: to.path })
73
+ return true
74
+ })
75
+ })
76
+
77
+ function isUsableToken(token: string, appTokenPrefix: string, clockSkewSeconds: number): boolean {
78
+ if (token.startsWith(appTokenPrefix)) {
79
+ return true
80
+ }
81
+
82
+ const payload = decodeJwtPayload(token)
83
+ if (!payload || typeof payload.exp !== 'number') {
84
+ return false
85
+ }
86
+
87
+ const nowSeconds = Math.floor(Date.now() / 1000)
88
+ return payload.exp > nowSeconds + clockSkewSeconds
89
+ }
90
+
91
+ function decodeJwtPayload(token: string): Record<string, unknown> | null {
92
+ const parts = token.split('.')
93
+ if (parts.length !== 3) {
94
+ return null
95
+ }
96
+
97
+ const payloadPart = parts[1]
98
+ if (!payloadPart) {
99
+ return null
100
+ }
101
+
102
+ try {
103
+ const padded = payloadPart.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(payloadPart.length / 4) * 4, '=')
104
+ const decoded = atob(padded)
105
+ const parsed = JSON.parse(decoded)
106
+ return parsed && typeof parsed === 'object' ? parsed as Record<string, unknown> : null
107
+ } catch {
108
+ return null
109
+ }
110
+ }
111
+
112
+ function logClient(event: string, details: Record<string, unknown>) {
113
+ if (!import.meta.dev) {
114
+ return
115
+ }
116
+
117
+ console.warn(`[AUTH GUARD CLIENT] ${event}`, details)
118
+ }
119
+
120
+ function requireString(value: unknown, key: string): string {
121
+ if (typeof value === 'string' && value.trim().length > 0) {
122
+ return value
123
+ }
124
+
125
+ throw new Error(`[AUTH GUARD CLIENT] Missing required config: ${key}`)
126
+ }
127
+
128
+ function requireNonNegativeNumber(value: unknown, key: string): number {
129
+ if (typeof value === 'number' && Number.isFinite(value) && value >= 0) {
130
+ return value
131
+ }
132
+
133
+ throw new Error(`[AUTH GUARD CLIENT] Invalid required numeric config: ${key}`)
134
+ }
package/eslint.config.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // @ts-check
2
- import withNuxt from './node_modules/.cache/nuxt/.nuxt/eslint.config.mjs'
2
+ import withNuxt from './.nuxt/eslint.config.mjs'
3
3
 
4
4
  export default withNuxt(
5
5
  // Your custom configs here