@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 +17 -0
- package/.github/workflows/publish.yml +66 -0
- package/.husky/commit-msg +5 -0
- package/.husky/pre-commit +32 -0
- package/.releaserc.json +1 -1
- package/CHANGELOG.md +23 -0
- package/README.md +78 -0
- package/SETUP_NPM_PUBLISH.md +134 -0
- package/app/composables/useAuth.ts +33 -2
- package/app/constants/auth.ts +9 -26
- package/app/plugins/auth-guard.client.ts +134 -0
- package/eslint.config.mjs +1 -1
- package/nuxt.config.ts +87 -11
- package/package.json +6 -3
- package/server/api/symfony/[...].ts +109 -83
- package/server/middleware/auth-guard.ts +144 -0
- package/server/utils/auth-constants.ts +9 -23
- package/shared/auth-constants.ts +12 -0
- package/app/middleware/auth.global.ts +0 -82
- package/pnpm-workspace.yaml +0 -5
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,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
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
|
|
110
|
+
// Clear auth cookies first so route middleware blocks protected pages immediately.
|
|
109
111
|
if (import.meta.client) {
|
|
110
|
-
|
|
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
|
+
}
|
package/app/constants/auth.ts
CHANGED
|
@@ -1,26 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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