nuxt-users 1.40.1 → 1.41.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/dist/cli.mjs CHANGED
@@ -5,7 +5,7 @@ import { defaultOptions } from './module.mjs';
5
5
  import { loadNuxt } from '@nuxt/kit';
6
6
  import { defu } from 'defu';
7
7
  import { runMigrations, createUser as createUser$1, createUsersTable as createUsersTable$1, createPersonalAccessTokensTable as createPersonalAccessTokensTable$1, createPasswordResetTokensTable as createPasswordResetTokensTable$1, createMigrationsTable as createMigrationsTable$1, useDb, checkTableExists, getAppliedMigrations } from '../dist/runtime/server/utils/index.js';
8
- import { g as getPasswordValidationOptions, v as validatePassword } from './shared/nuxt-users.DfZ7bc_P.mjs';
8
+ import { g as getPasswordValidationOptions, v as validatePassword } from './shared/nuxt-users.Cvjgn5KJ.mjs';
9
9
  import '../dist/runtime/locales/index.js';
10
10
  import '../dist/runtime/utils/locale.js';
11
11
  import '../dist/runtime/utils/deep-merge.js';
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.0.0"
6
6
  },
7
- "version": "1.40.1",
7
+ "version": "1.41.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.2",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -162,6 +162,10 @@ const module$1 = defineNuxtModule({
162
162
  src: resolver.resolve("./runtime/plugin.auth-init"),
163
163
  name: "0-auth-init-nuxt-users"
164
164
  });
165
+ addPlugin({
166
+ src: resolver.resolve("./runtime/plugin.theme"),
167
+ mode: "client"
168
+ });
165
169
  addPlugin({
166
170
  src: resolver.resolve("./runtime/plugin"),
167
171
  mode: "server"
@@ -1 +1 @@
1
- :root{--nu-color-primary:#3b82f6;--nu-color-primary-dark:#2563eb;--nu-color-primary-light:#60a5fa;--nu-color-white:#fff;--nu-color-gray-50:#f9fafb;--nu-color-gray-500:#6b7280;--nu-color-gray-600:#4b5563;--nu-color-gray-700:#374151;--nu-color-gray-900:#111827;--nu-color-success:#059669;--nu-color-success-light:#f0fdf4;--nu-color-success-border:#9ae6b4;--nu-color-success-text:#047857;--nu-color-error:#dc2626;--nu-color-error-light:#fef2f2;--nu-color-error-border:#fecaca;--nu-color-error-alt:#db5050;--nu-color-error-dark:#b91c1c;--nu-color-border:#e5e7eb;--nu-color-border-light:#d1d5db;--nu-color-border-dark:#ccc;--nu-color-bg-primary:#fff;--nu-color-bg-secondary:#f9fafb;--nu-color-bg-tertiary:#f3f4f6;--nu-color-text-primary:#000;--nu-color-text-secondary:#6b7280;--nu-color-strength-bg:#e9ecef;--nu-color-validation-passed:#15803d;--nu-color-validation-failed:#dc3545;--nu-color-validation-hint:#6c757d}.dark :root{--nu-color-primary:#60a5fa;--nu-color-primary-dark:#3b82f6;--nu-color-primary-light:#93c5fd;--nu-color-white:#000;--nu-color-gray-50:#111827;--nu-color-gray-500:#9ca3af;--nu-color-gray-600:#d1d5db;--nu-color-gray-700:#e5e7eb;--nu-color-gray-900:#f9fafb;--nu-color-success:#10b981;--nu-color-success-light:#064e3b;--nu-color-success-border:#065f46;--nu-color-success-text:#6ee7b7;--nu-color-error:#ef4444;--nu-color-error-light:#450a0a;--nu-color-error-border:#7f1d1d;--nu-color-error-alt:#f87171;--nu-color-error-dark:#dc2626;--nu-color-border:#374151;--nu-color-border-light:#4b5563;--nu-color-border-dark:#6b7280;--nu-color-bg-primary:#111827;--nu-color-bg-secondary:#1f2937;--nu-color-bg-tertiary:#374151;--nu-color-text-primary:#fff;--nu-color-text-secondary:#9ca3af;--nu-color-strength-bg:#374151;--nu-color-validation-passed:#22c55e;--nu-color-validation-failed:#ef4444;--nu-color-validation-hint:#9ca3af}.n-users-form-group{display:flex;flex-direction:column;gap:.5rem;margin-bottom:1rem;width:100%}.n-users-form-group.n-users-form-group-narrow{width:90%}.n-users-form-group label,.n-users-form-label{color:var(--nu-color-text-primary,#000);display:block;font-size:1rem;font-weight:500;margin-bottom:.5rem}.n-users-form-group input,.n-users-form-group select,.n-users-form-input{border:1px solid var(--nu-color-border,#ccc);border-radius:.25rem;font-size:1rem;padding:.5rem;transition:border-color .2s,box-shadow .2s;width:100%}.n-users-form-group input:focus,.n-users-form-group select:focus,.n-users-form-input:focus{border-color:var(--nu-color-primary);box-shadow:0 0 0 .1rem rgba(59,130,246,.1);outline:none}.n-users-form-group input:disabled,.n-users-form-input:disabled{background-color:var(--nu-color-bg-secondary,#f9fafb);cursor:not-allowed}.n-users-form-group input.error,.n-users-form-input.error{border-color:var(--nu-color-error,#dc3545)}.n-users-form-group input[type=email],.n-users-form-group input[type=password],.n-users-form-group input[type=text]{background-color:var(--nu-color-bg-secondary);border:1px solid var(--nu-color-border-light);border-radius:.5em;box-sizing:border-box;color:var(--nu-color-gray-700);font-size:.875rem;padding:.75em 1em;transition:all .2s ease-in-out;width:100%}.n-users-form-group input[type=email]:focus,.n-users-form-group input[type=password]:focus,.n-users-form-group input[type=text]:focus{background-color:var(--nu-color-bg-primary);border-color:var(--nu-color-primary);box-shadow:0 0 0 3px rgba(59,130,246,.1);outline:none}.n-users-form-group input[type=checkbox]{margin:0;width:auto}.n-users-google-btn{align-items:center;border:none;border-radius:.25rem;cursor:pointer;display:inline-flex;font-size:1rem;font-weight:500;gap:8px;justify-content:center;padding:.5rem 1rem;text-decoration:none;transition:background-color .2s,transform .1s}.n-users-google-logo{flex-shrink:0}.n-users-btn,.n-users-cancel-btn,.n-users-delete-btn,.n-users-edit-btn,.n-users-submit-btn{align-self:flex-start;border:none;border-radius:.25rem;cursor:pointer;display:inline-block;font-size:1rem;font-weight:500;padding:.5rem 1rem;text-decoration:none;transition:background-color .2s,transform .1s}.n-users-btn:hover,.n-users-edit-btn:hover,.n-users-submit-btn:hover{background-color:var(--nu-color-primary-dark,#2563eb);transform:translateY(-1px)}.n-users-btn:disabled,.n-users-submit-btn:disabled{cursor:not-allowed;opacity:.6;transform:none}.n-users-btn-primary,.n-users-edit-btn,.n-users-submit-btn{background-color:var(--nu-color-primary,#3b82f6);color:#fff}.n-users-btn-secondary,.n-users-cancel-btn{background-color:var(--nu-color-gray-600,#4b5563);color:#fff}.n-users-cancel-btn:hover{background-color:var(--nu-color-gray-700,#374151)}.n-users-delete-btn{background-color:var(--nu-color-error-dark,#b91c1c);color:#fff}.n-users-delete-btn:hover{background-color:#991b1b}.n-users-login-section button[type=submit],.n-users-register-section button[type=submit],.n-users-reset-password-container button[type=submit],.n-users-user-form button[type=submit],form.n-users-form button[type=submit]{align-items:center;background-color:var(--nu-color-primary);border:1px solid var(--nu-color-primary);border-radius:.5em;color:var(--nu-color-white);cursor:pointer;display:flex;font-size:.875rem;font-weight:500;gap:.5rem;justify-content:center;min-height:44px;padding:.75em 1em;transition:all .2s ease-in-out;width:100%}.n-users-login-section button[type=submit]:hover:not(:disabled),.n-users-register-section button[type=submit]:hover:not(:disabled),.n-users-reset-password-container button[type=submit]:hover:not(:disabled),.n-users-user-form button[type=submit]:hover:not(:disabled),form.n-users-form button[type=submit]:hover:not(:disabled){background-color:var(--nu-color-primary-dark);border-color:var(--nu-color-primary-dark)}.n-users-login-section button[type=submit]:disabled,.n-users-register-section button[type=submit]:disabled,.n-users-reset-password-container button[type=submit]:disabled,.n-users-user-form button[type=submit]:disabled,form.n-users-form button[type=submit]:disabled{cursor:not-allowed;opacity:.6}.n-users-error-message,.n-users-error-text{background-color:var(--nu-color-error-light,#fef2f2);border:1px solid var(--nu-color-error-border,#fecaca);border-radius:.5rem;color:var(--nu-color-error-dark,#b91c1c);font-size:.875rem;margin-top:.5rem;padding:.75rem 1rem}.n-users-success-message{background-color:var(--nu-color-success-light,#f0fdf4);border:1px solid var(--nu-color-success-border,#9ae6b4);border-radius:.5rem;color:var(--nu-color-success-text,#047857);font-size:.875rem;margin-top:.5rem;padding:.75rem 1rem}.n-users-grid{display:grid;gap:1rem}.n-users-grid-2{grid-template-columns:1fr 5fr}.n-users-grid-auto{grid-template-columns:repeat(auto-fill,minmax(12em,1fr))}.n-users-form-actions{align-items:center;display:flex;gap:1rem;margin-top:1.5rem}.n-users-loading-spinner{animation:n-users-spin 1s linear infinite;border:2px solid var(--nu-color-white,#fff);border-radius:50%;border-top:2px solid transparent;height:16px;width:16px}@keyframes n-users-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.n-users-card{background-color:var(--nu-color-bg-primary,#fff);border:1px solid var(--nu-color-border,#e5e7eb);border-radius:.5rem;padding:1rem}.n-users-section{margin:0 auto;max-width:42em;padding:1em}.n-users-section-header{border-bottom:.1em solid var(--nu-color-border,#e5e7eb);margin-bottom:1rem;padding-bottom:.5rem}.n-users-form-help{color:var(--nu-color-text-secondary,#6b7280);font-size:.75rem;margin-top:.25rem}.n-users-strength-bar{background-color:var(--nu-color-strength-bg,#e9ecef);border-radius:2px;height:4px;margin-bottom:4px;overflow:hidden;width:100%}.n-users-strength-fill{height:100%;transition:width .3s ease,background-color .3s ease}.n-users-strength-text{font-size:.75rem;font-weight:500}.n-users-validation-container{background-color:#f8f9fa;border:1px solid #e9ecef;border-radius:4px;margin-top:12px;padding:12px}.n-users-container-title{color:#495057;font-size:.875rem;font-weight:600;margin-bottom:8px}.n-users-list-unstyled{list-style:none;margin:0;padding-left:0}.n-users-list-item{align-items:center;display:flex;font-size:.875rem;margin-bottom:6px;transition:color .2s ease}.n-users-list-item:last-child{margin-bottom:0}.n-users-icon{font-weight:700;margin-right:8px;min-width:16px;text-align:center}.n-users-text{flex:1}.n-users-list ul{list-style:none}.n-users-login-section form,.n-users-register-section form,.n-users-reset-password-container form,.n-users-user-form form,form.n-users-form{display:flex;flex-direction:column;gap:1.5rem}.n-users-login-section{background:linear-gradient(135deg,var(--nu-color-bg-primary) 0,var(--nu-color-bg-secondary) 100%);border:1px solid var(--nu-color-border);border-radius:.75em;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);max-width:25rem}.n-users-login-header{margin-bottom:1em;text-align:center}.n-users-login-title{color:var(--nu-color-gray-700);font-size:1.875rem;font-weight:700;line-height:1.2;margin:0 0 .5em}.n-users-login-subtitle{color:var(--nu-color-gray-700);font-size:.875rem;line-height:1.5;margin:0}.n-users-remember-me{align-items:center;display:flex;gap:.5rem}.n-users-remember-me label{color:var(--nu-color-gray-700);font-size:.875rem;margin:0}.n-users-login-footer{margin-top:1em;text-align:center}.n-users-forgot-password{margin:0}.n-users-forgot-link{align-items:center;color:var(--nu-color-primary);display:inline-flex;font-size:.875rem;gap:.5rem;text-decoration:none;transition:color .2s ease-in-out}.n-users-forgot-link:hover:not(.loading){color:var(--nu-color-primary-dark);text-decoration:underline}.n-users-forgot-link.loading{cursor:not-allowed;opacity:.6;pointer-events:none}.n-users-password-strength{margin-top:8px}.n-users-validation-rules{margin-top:12px}.n-users-rules-title{margin-bottom:8px}.n-users-rules-list{padding-left:0}.n-users-rule-item{margin-bottom:6px}.n-users-rule-item:last-child{margin-bottom:0}.n-users-rule-passed{color:var(--nu-color-validation-passed,#15803d)}.n-users-rule-failed{color:var(--nu-color-validation-failed,#dc3545)}.n-users-rule-icon{margin-right:8px}.n-users-rule-text{flex:1}.n-users-validation-errors{margin-top:8px}.n-users-error-text:last-child{margin-bottom:0}.n-users-password-hints{margin-top:8px}.n-users-hint-title{margin-bottom:8px}.n-users-hint-list{padding-left:16px}.n-users-hint-item{color:var(--nu-color-validation-hint,#6c757d);margin-bottom:4px}.n-users-hint-item:last-child{margin-bottom:0}.n-users-logout-link{background:var(--nu-color-error-dark,#b91c1c);border:2px solid transparent;border-color:var(--nu-color-error-dark);border-radius:.5em;color:#fff;padding:.5em 1em;text-decoration:none}.n-users-logout-link:hover{border-color:var(--nu-color-primary)}.n-users-user-card dd{margin:0}.n-users-user-card-actions{display:flex;gap:.5rem;margin-top:1rem}.n-users-user-form{margin:0 auto;max-width:500px}.n-users-form-group input.error{border-color:#dc3545}.n-users-profile-info{background-color:var(--nu-color-bg-primary);border-top-left-radius:.5em;border-top-right-radius:.5em}.n-users-profile-details{font-size:1rem;list-style:none;margin:0;padding:0}.n-users-reset-password-container{background-color:var(--nu-color-bg-primary);border-bottom-left-radius:.5em;border-bottom-right-radius:.5em}@media (max-width:480px){.n-users-login-title{font-size:1.5rem}}.n-users-list-enter-active,.n-users-list-leave-active,.n-users-list-move{transition:all .3s ease}.n-users-list-enter-from,.n-users-list-leave-to{opacity:0;transform:translateX(2rem)}.n-users-list-leave-active{position:absolute}.n-users-list-move{transition-timing-function:ease}
1
+ :root{--nu-color-primary:#3b82f6;--nu-color-primary-dark:#2563eb;--nu-color-primary-light:#60a5fa;--nu-color-white:#fff;--nu-color-gray-50:#f9fafb;--nu-color-gray-500:#6b7280;--nu-color-gray-600:#4b5563;--nu-color-gray-700:#374151;--nu-color-gray-900:#111827;--nu-color-success:#059669;--nu-color-success-light:#f0fdf4;--nu-color-success-border:#9ae6b4;--nu-color-success-text:#047857;--nu-color-error:#dc2626;--nu-color-error-light:#fef2f2;--nu-color-error-border:#fecaca;--nu-color-error-alt:#db5050;--nu-color-error-dark:#b91c1c;--nu-color-border:#e5e7eb;--nu-color-border-light:#d1d5db;--nu-color-border-dark:#ccc;--nu-color-bg-primary:#fff;--nu-color-bg-secondary:#f9fafb;--nu-color-bg-tertiary:#f3f4f6;--nu-color-text-primary:#000;--nu-color-text-secondary:#6b7280;--nu-color-strength-bg:#e9ecef;--nu-color-validation-passed:#15803d;--nu-color-validation-failed:#dc3545;--nu-color-validation-hint:#6c757d}@media (prefers-color-scheme:dark){:root{--nu-color-primary:#60a5fa;--nu-color-primary-dark:#3b82f6;--nu-color-primary-light:#93c5fd;--nu-color-white:#000;--nu-color-gray-50:#111827;--nu-color-gray-500:#9ca3af;--nu-color-gray-600:#d1d5db;--nu-color-gray-700:#e5e7eb;--nu-color-gray-900:#f9fafb;--nu-color-success:#10b981;--nu-color-success-light:#064e3b;--nu-color-success-border:#065f46;--nu-color-success-text:#6ee7b7;--nu-color-error:#ef4444;--nu-color-error-light:#450a0a;--nu-color-error-border:#7f1d1d;--nu-color-error-alt:#f87171;--nu-color-error-dark:#dc2626;--nu-color-border:#374151;--nu-color-border-light:#4b5563;--nu-color-border-dark:#6b7280;--nu-color-bg-primary:#111827;--nu-color-bg-secondary:#1f2937;--nu-color-bg-tertiary:#374151;--nu-color-text-primary:#fff;--nu-color-text-secondary:#9ca3af;--nu-color-strength-bg:#374151;--nu-color-validation-passed:#22c55e;--nu-color-validation-failed:#ef4444;--nu-color-validation-hint:#9ca3af}}:root.dark{--nu-color-primary:#60a5fa;--nu-color-primary-dark:#3b82f6;--nu-color-primary-light:#93c5fd;--nu-color-white:#000;--nu-color-gray-50:#111827;--nu-color-gray-500:#9ca3af;--nu-color-gray-600:#d1d5db;--nu-color-gray-700:#e5e7eb;--nu-color-gray-900:#f9fafb;--nu-color-success:#10b981;--nu-color-success-light:#064e3b;--nu-color-success-border:#065f46;--nu-color-success-text:#6ee7b7;--nu-color-error:#ef4444;--nu-color-error-light:#450a0a;--nu-color-error-border:#7f1d1d;--nu-color-error-alt:#f87171;--nu-color-error-dark:#dc2626;--nu-color-border:#374151;--nu-color-border-light:#4b5563;--nu-color-border-dark:#6b7280;--nu-color-bg-primary:#111827;--nu-color-bg-secondary:#1f2937;--nu-color-bg-tertiary:#374151;--nu-color-text-primary:#fff;--nu-color-text-secondary:#9ca3af;--nu-color-strength-bg:#374151;--nu-color-validation-passed:#22c55e;--nu-color-validation-failed:#ef4444;--nu-color-validation-hint:#9ca3af}.n-users-form-group{display:flex;flex-direction:column;gap:.5rem;margin-bottom:1rem;width:100%}.n-users-form-group.n-users-form-group-narrow{width:90%}.n-users-form-group label,.n-users-form-label{color:var(--nu-color-text-primary,#000);display:block;font-size:1rem;font-weight:500;margin-bottom:.5rem}.n-users-form-group input,.n-users-form-group select,.n-users-form-input{border:1px solid var(--nu-color-border,#ccc);border-radius:.25rem;font-size:1rem;padding:.5rem;transition:border-color .2s,box-shadow .2s;width:100%}.n-users-form-group input:focus,.n-users-form-group select:focus,.n-users-form-input:focus{border-color:var(--nu-color-primary);box-shadow:0 0 0 .1rem rgba(59,130,246,.1);outline:none}.n-users-form-group input:disabled,.n-users-form-input:disabled{background-color:var(--nu-color-bg-secondary,#f9fafb);cursor:not-allowed}.n-users-form-group input.error,.n-users-form-input.error{border-color:var(--nu-color-error,#dc3545)}.n-users-form-group input[type=email],.n-users-form-group input[type=password],.n-users-form-group input[type=text]{background-color:var(--nu-color-bg-secondary);border:1px solid var(--nu-color-border-light);border-radius:.5em;box-sizing:border-box;color:var(--nu-color-gray-700);font-size:.875rem;padding:.75em 1em;transition:all .2s ease-in-out;width:100%}.n-users-form-group input[type=email]:focus,.n-users-form-group input[type=password]:focus,.n-users-form-group input[type=text]:focus{background-color:var(--nu-color-bg-primary);border-color:var(--nu-color-primary);box-shadow:0 0 0 3px rgba(59,130,246,.1);outline:none}.n-users-form-group input[type=checkbox]{margin:0;width:auto}.n-users-google-btn{align-items:center;border:none;border-radius:.25rem;cursor:pointer;display:inline-flex;font-size:1rem;font-weight:500;gap:8px;justify-content:center;padding:.5rem 1rem;text-decoration:none;transition:background-color .2s,transform .1s}.n-users-google-logo{flex-shrink:0}.n-users-btn,.n-users-cancel-btn,.n-users-delete-btn,.n-users-edit-btn,.n-users-submit-btn{align-self:flex-start;border:none;border-radius:.25rem;cursor:pointer;display:inline-block;font-size:1rem;font-weight:500;padding:.5rem 1rem;text-decoration:none;transition:background-color .2s,transform .1s}.n-users-btn:hover,.n-users-edit-btn:hover,.n-users-submit-btn:hover{background-color:var(--nu-color-primary-dark,#2563eb);transform:translateY(-1px)}.n-users-btn:disabled,.n-users-submit-btn:disabled{cursor:not-allowed;opacity:.6;transform:none}.n-users-btn-primary,.n-users-edit-btn,.n-users-submit-btn{background-color:var(--nu-color-primary,#3b82f6);color:#fff}.n-users-btn-secondary,.n-users-cancel-btn{background-color:var(--nu-color-gray-600,#4b5563);color:#fff}.n-users-cancel-btn:hover{background-color:var(--nu-color-gray-700,#374151)}.n-users-delete-btn{background-color:var(--nu-color-error-dark,#b91c1c);color:#fff}.n-users-delete-btn:hover{background-color:#991b1b}.n-users-login-section button[type=submit],.n-users-register-section button[type=submit],.n-users-reset-password-container button[type=submit],.n-users-user-form button[type=submit],form.n-users-form button[type=submit]{align-items:center;background-color:var(--nu-color-primary);border:1px solid var(--nu-color-primary);border-radius:.5em;color:var(--nu-color-white);cursor:pointer;display:flex;font-size:.875rem;font-weight:500;gap:.5rem;justify-content:center;min-height:44px;padding:.75em 1em;transition:all .2s ease-in-out;width:100%}.n-users-login-section button[type=submit]:hover:not(:disabled),.n-users-register-section button[type=submit]:hover:not(:disabled),.n-users-reset-password-container button[type=submit]:hover:not(:disabled),.n-users-user-form button[type=submit]:hover:not(:disabled),form.n-users-form button[type=submit]:hover:not(:disabled){background-color:var(--nu-color-primary-dark);border-color:var(--nu-color-primary-dark)}.n-users-login-section button[type=submit]:disabled,.n-users-register-section button[type=submit]:disabled,.n-users-reset-password-container button[type=submit]:disabled,.n-users-user-form button[type=submit]:disabled,form.n-users-form button[type=submit]:disabled{cursor:not-allowed;opacity:.6}.n-users-error-message,.n-users-error-text{background-color:var(--nu-color-error-light,#fef2f2);border:1px solid var(--nu-color-error-border,#fecaca);border-radius:.5rem;color:var(--nu-color-error-dark,#b91c1c);font-size:.875rem;margin-top:.5rem;padding:.75rem 1rem}.n-users-success-message{background-color:var(--nu-color-success-light,#f0fdf4);border:1px solid var(--nu-color-success-border,#9ae6b4);border-radius:.5rem;color:var(--nu-color-success-text,#047857);font-size:.875rem;margin-top:.5rem;padding:.75rem 1rem}.n-users-grid{display:grid;gap:1rem}.n-users-grid-2{grid-template-columns:1fr 5fr}.n-users-grid-auto{grid-template-columns:repeat(auto-fill,minmax(12em,1fr))}.n-users-form-actions{align-items:center;display:flex;gap:1rem;margin-top:1.5rem}.n-users-loading-spinner{animation:n-users-spin 1s linear infinite;border:2px solid var(--nu-color-white,#fff);border-radius:50%;border-top:2px solid transparent;height:16px;width:16px}@keyframes n-users-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.n-users-card{background-color:var(--nu-color-bg-primary,#fff);border:1px solid var(--nu-color-border,#e5e7eb);border-radius:.5rem;padding:1rem}.n-users-section{margin:0 auto;max-width:42em;padding:1em}.n-users-section-header{border-bottom:.1em solid var(--nu-color-border,#e5e7eb);margin-bottom:1rem;padding-bottom:.5rem}.n-users-form-help{color:var(--nu-color-text-secondary,#6b7280);font-size:.75rem;margin-top:.25rem}.n-users-strength-bar{background-color:var(--nu-color-strength-bg,#e9ecef);border-radius:2px;height:4px;margin-bottom:4px;overflow:hidden;width:100%}.n-users-strength-fill{height:100%;transition:width .3s ease,background-color .3s ease}.n-users-strength-text{font-size:.75rem;font-weight:500}.n-users-validation-container{background-color:#f8f9fa;border:1px solid #e9ecef;border-radius:4px;margin-top:12px;padding:12px}.n-users-container-title{color:#495057;font-size:.875rem;font-weight:600;margin-bottom:8px}.n-users-list-unstyled{list-style:none;margin:0;padding-left:0}.n-users-list-item{align-items:center;display:flex;font-size:.875rem;margin-bottom:6px;transition:color .2s ease}.n-users-list-item:last-child{margin-bottom:0}.n-users-icon{font-weight:700;margin-right:8px;min-width:16px;text-align:center}.n-users-text{flex:1}.n-users-list ul{list-style:none}.n-users-login-section form,.n-users-register-section form,.n-users-reset-password-container form,.n-users-user-form form,form.n-users-form{display:flex;flex-direction:column;gap:1.5rem}.n-users-login-section{background:linear-gradient(135deg,var(--nu-color-bg-primary) 0,var(--nu-color-bg-secondary) 100%);border:1px solid var(--nu-color-border);border-radius:.75em;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);max-width:25rem}.n-users-login-header{margin-bottom:1em;text-align:center}.n-users-login-title{color:var(--nu-color-gray-700);font-size:1.875rem;font-weight:700;line-height:1.2;margin:0 0 .5em}.n-users-login-subtitle{color:var(--nu-color-gray-700);font-size:.875rem;line-height:1.5;margin:0}.n-users-remember-me{align-items:center;display:flex;gap:.5rem}.n-users-remember-me label{color:var(--nu-color-gray-700);font-size:.875rem;margin:0}.n-users-login-footer{margin-top:1em;text-align:center}.n-users-forgot-password{margin:0}.n-users-forgot-link{align-items:center;color:var(--nu-color-primary);display:inline-flex;font-size:.875rem;gap:.5rem;text-decoration:none;transition:color .2s ease-in-out}.n-users-forgot-link:hover:not(.loading){color:var(--nu-color-primary-dark);text-decoration:underline}.n-users-forgot-link.loading{cursor:not-allowed;opacity:.6;pointer-events:none}.n-users-link{color:var(--nu-color-primary);font-size:.875rem;text-decoration:none;transition:color .2s ease-in-out}.n-users-link:hover{color:var(--nu-color-primary-dark);text-decoration:underline}.n-users-link:visited{color:var(--nu-color-primary)}.n-users-link:visited:hover{color:var(--nu-color-primary-dark)}.n-users-password-strength{margin-top:8px}.n-users-validation-rules{margin-top:12px}.n-users-rules-title{margin-bottom:8px}.n-users-rules-list{padding-left:0}.n-users-rule-item{margin-bottom:6px}.n-users-rule-item:last-child{margin-bottom:0}.n-users-rule-passed{color:var(--nu-color-validation-passed,#15803d)}.n-users-rule-failed{color:var(--nu-color-validation-failed,#dc3545)}.n-users-rule-icon{margin-right:8px}.n-users-rule-text{flex:1}.n-users-validation-errors{margin-top:8px}.n-users-error-text:last-child{margin-bottom:0}.n-users-password-hints{margin-top:8px}.n-users-hint-title{margin-bottom:8px}.n-users-hint-list{padding-left:16px}.n-users-hint-item{color:var(--nu-color-validation-hint,#6c757d);margin-bottom:4px}.n-users-hint-item:last-child{margin-bottom:0}.n-users-logout-link{background:var(--nu-color-error-dark,#b91c1c);border:2px solid transparent;border-color:var(--nu-color-error-dark);border-radius:.5em;color:#fff;padding:.5em 1em;text-decoration:none}.n-users-logout-link:hover{border-color:var(--nu-color-primary)}.n-users-user-card dd{margin:0}.n-users-user-card-actions{display:flex;gap:.5rem;margin-top:1rem}.n-users-user-form{margin:0 auto;max-width:500px}.n-users-form-group input.error{border-color:#dc3545}.n-users-profile-info{background-color:var(--nu-color-bg-primary);border-top-left-radius:.5em;border-top-right-radius:.5em}.n-users-profile-details{font-size:1rem;list-style:none;margin:0;padding:0}.n-users-reset-password-container{background-color:var(--nu-color-bg-primary);border-bottom-left-radius:.5em;border-bottom-right-radius:.5em}@media (max-width:480px){.n-users-login-title{font-size:1.5rem}}.n-users-list-enter-active,.n-users-list-leave-active,.n-users-list-move{transition:all .3s ease}.n-users-list-enter-from,.n-users-list-leave-to{opacity:0;transform:translateX(2rem)}.n-users-list-leave-active{position:absolute}.n-users-list-move{transition-timing-function:ease}
@@ -136,7 +136,7 @@ const handleForgotPassword = async () => {
136
136
  name="password"
137
137
  :placeholder="props.passwordPlaceholder || t('login.passwordPlaceholder')"
138
138
  required
139
- :minlength="passwordValidation.minLength"
139
+ :minlength="passwordValidation?.minLength || 8"
140
140
  >
141
141
  </div>
142
142
  </slot>
@@ -23,43 +23,52 @@ const hasPassword = computed(() => props.password?.length > 0);
23
23
  const validationRules = computed(() => {
24
24
  if (!props.password) return [];
25
25
  const rules = [];
26
- if (moduleOptions.passwordValidation.minLength) {
26
+ if (moduleOptions.passwordValidation?.minLength) {
27
27
  const hasMinLength = props.password.length >= moduleOptions.passwordValidation.minLength;
28
28
  rules.push({
29
- text: t("passwordStrength.minLength", [moduleOptions.passwordValidation.minLength]),
29
+ text: t("passwordStrength.requirements.minLength", [moduleOptions.passwordValidation.minLength]),
30
30
  passed: hasMinLength
31
31
  });
32
32
  }
33
- if (moduleOptions.passwordValidation.requireUppercase) {
33
+ if (moduleOptions.passwordValidation?.requireUppercase) {
34
34
  const hasUppercase = /[A-Z]/.test(props.password);
35
35
  rules.push({
36
- text: t("passwordStrength.uppercase"),
36
+ text: t("passwordStrength.requirements.uppercase"),
37
37
  passed: hasUppercase
38
38
  });
39
39
  }
40
- if (moduleOptions.passwordValidation.requireLowercase) {
40
+ if (moduleOptions.passwordValidation?.requireLowercase) {
41
41
  const hasLowercase = /[a-z]/.test(props.password);
42
42
  rules.push({
43
- text: t("passwordStrength.lowercase"),
43
+ text: t("passwordStrength.requirements.lowercase"),
44
44
  passed: hasLowercase
45
45
  });
46
46
  }
47
- if (moduleOptions.passwordValidation.requireNumbers) {
47
+ if (moduleOptions.passwordValidation?.requireNumbers) {
48
48
  const hasNumbers = /\d/.test(props.password);
49
49
  rules.push({
50
- text: t("passwordStrength.number"),
50
+ text: t("passwordStrength.requirements.numbers"),
51
51
  passed: hasNumbers
52
52
  });
53
53
  }
54
- if (moduleOptions.passwordValidation.requireSpecialChars) {
54
+ if (moduleOptions.passwordValidation?.requireSpecialChars) {
55
55
  const hasSpecialChars = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(props.password);
56
56
  rules.push({
57
- text: t("passwordStrength.specialChar"),
57
+ text: t("passwordStrength.requirements.specialChars"),
58
58
  passed: hasSpecialChars
59
59
  });
60
60
  }
61
61
  return rules;
62
62
  });
63
+ const resolveTranslation = (message) => {
64
+ if (!message) return "";
65
+ if (message.includes("|")) {
66
+ const [key = "", paramStr] = message.split("|");
67
+ const params = paramStr ? paramStr.split(",") : [];
68
+ return t(key, params);
69
+ }
70
+ return t(message);
71
+ };
63
72
  </script>
64
73
 
65
74
  <template>
@@ -82,7 +91,7 @@ const validationRules = computed(() => {
82
91
  color: validationResult?.strength === 'weak' ? '#dc3545' : validationResult?.strength === 'medium' ? '#ffc107' : validationResult?.strength === 'strong' ? '#28a745' : '#6c757d'
83
92
  }"
84
93
  >
85
- {{ validationResult?.strength === "weak" ? props.weakLabel || t("passwordStrength.weak") : validationResult?.strength === "medium" ? props.mediumLabel || t("passwordStrength.medium") : validationResult?.strength === "strong" ? props.strongLabel || t("passwordStrength.strong") : props.unknownLabel || t("passwordStrength.unknown") }}
94
+ {{ validationResult?.strength === "weak" ? props.weakLabel || t("passwordStrength.strength.weak") : validationResult?.strength === "medium" ? props.mediumLabel || t("passwordStrength.strength.medium") : validationResult?.strength === "strong" ? props.strongLabel || t("passwordStrength.strength.strong") : props.unknownLabel || t("passwordStrength.strength.unknown") }}
86
95
  ({{ validationResult?.score || 0 }}%)
87
96
  </span>
88
97
  </div>
@@ -122,7 +131,7 @@ const validationRules = computed(() => {
122
131
  :key="String(error)"
123
132
  class="n-users-error-text"
124
133
  >
125
- {{ error }}
134
+ {{ resolveTranslation(error) }}
126
135
  </small>
127
136
  </div>
128
137
 
@@ -140,7 +149,7 @@ const validationRules = computed(() => {
140
149
  :key="'hint-' + hint"
141
150
  class="n-users-hint-item n-users-list-item"
142
151
  >
143
- {{ hint }}
152
+ {{ resolveTranslation(hint) }}
144
153
  </li>
145
154
  </ul>
146
155
  </div>
@@ -150,7 +150,7 @@ const handleSubmit = async () => {
150
150
  name="password"
151
151
  :placeholder="props.passwordPlaceholder || t('register.passwordPlaceholder')"
152
152
  required
153
- :minlength="passwordValidation.minLength"
153
+ :minlength="passwordValidation?.minLength || 8"
154
154
  >
155
155
  </div>
156
156
  </slot>
@@ -11,11 +11,11 @@ declare var __VLS_1: {
11
11
  user: any;
12
12
  index: number;
13
13
  }, __VLS_3: {
14
- canEdit: any;
14
+ canEdit: boolean;
15
15
  editUser: () => Promise<void>;
16
16
  user: any;
17
17
  }, __VLS_5: {
18
- canDelete: any;
18
+ canDelete: boolean;
19
19
  deleteUser: () => Promise<void>;
20
20
  user: any;
21
21
  };
@@ -25,9 +25,16 @@ const canEdit = computed(() => {
25
25
  if (nuxtUsers.auth?.permissions) {
26
26
  const userPermissions = nuxtUsers.auth.permissions[currentUser.value.role] || [];
27
27
  const apiBasePath = nuxtUsers.apiBasePath;
28
- return userPermissions.some(
29
- (permission) => permission === "*" || permission === `${apiBasePath}/*` || permission === `${apiBasePath}/[id].put` || permission === `${apiBasePath}/[id].patch`
30
- );
28
+ return userPermissions.some((permission) => {
29
+ if (typeof permission === "string") {
30
+ return permission === "*" || permission === `${apiBasePath}/*` || permission === `${apiBasePath}/[id].put` || permission === `${apiBasePath}/[id].patch`;
31
+ }
32
+ const pathMatches = permission.path === "*" || permission.path === `${apiBasePath}/*` || permission.path === `${apiBasePath}/[id].put` || permission.path === `${apiBasePath}/[id].patch`;
33
+ if (!pathMatches) {
34
+ return false;
35
+ }
36
+ return permission.methods.some((method) => method.toUpperCase() === "PUT" || method.toUpperCase() === "PATCH");
37
+ });
31
38
  }
32
39
  return false;
33
40
  });
@@ -36,9 +43,16 @@ const canDelete = computed(() => {
36
43
  if (nuxtUsers.auth?.permissions) {
37
44
  const userPermissions = nuxtUsers.auth.permissions[currentUser.value.role] || [];
38
45
  const apiBasePath = nuxtUsers.apiBasePath;
39
- return userPermissions.some(
40
- (permission) => permission === "*" || permission === `${apiBasePath}/*` || permission === `${apiBasePath}/[id].delete`
41
- );
46
+ return userPermissions.some((permission) => {
47
+ if (typeof permission === "string") {
48
+ return permission === "*" || permission === `${apiBasePath}/*` || permission === `${apiBasePath}/[id].delete`;
49
+ }
50
+ const pathMatches = permission.path === "*" || permission.path === `${apiBasePath}/*` || permission.path === `${apiBasePath}/[id].delete`;
51
+ if (!pathMatches) {
52
+ return false;
53
+ }
54
+ return permission.methods.some((method) => method.toUpperCase() === "DELETE");
55
+ });
42
56
  }
43
57
  return false;
44
58
  });
@@ -11,11 +11,11 @@ declare var __VLS_1: {
11
11
  user: any;
12
12
  index: number;
13
13
  }, __VLS_3: {
14
- canEdit: any;
14
+ canEdit: boolean;
15
15
  editUser: () => Promise<void>;
16
16
  user: any;
17
17
  }, __VLS_5: {
18
- canDelete: any;
18
+ canDelete: boolean;
19
19
  deleteUser: () => Promise<void>;
20
20
  user: any;
21
21
  };
@@ -1,5 +1,5 @@
1
1
  import { useRuntimeConfig } from "#app";
2
- import { NO_AUTH_PATHS, NO_AUTH_API_PATHS } from "../constants.js";
2
+ import { PUBLIC_PAGES, PUBLIC_API_ENDPOINTS, AUTHENTICATED_AUTO_ACCESS_ENDPOINTS } from "../constants.js";
3
3
  import { hasPermission, isWhitelisted } from "../utils/permissions.js";
4
4
  import { useAuthentication } from "./useAuthentication.js";
5
5
  export const usePublicPaths = () => {
@@ -8,13 +8,13 @@ export const usePublicPaths = () => {
8
8
  const apiBasePath = config.apiBasePath || "/api/nuxt-users";
9
9
  const { user } = useAuthentication();
10
10
  const getPublicPaths = () => {
11
- const noAuthPaths = [...NO_AUTH_PATHS];
12
- const noAuthApiPaths = NO_AUTH_API_PATHS.map((path) => `${apiBasePath}${path}`);
11
+ const publicPages = [...PUBLIC_PAGES];
12
+ const publicApiEndpoints = PUBLIC_API_ENDPOINTS.map((endpoint) => `${endpoint.methods.join(", ")}: ${apiBasePath}${endpoint.path}`);
13
13
  const whitelistedPaths = config.auth?.whitelist || [];
14
14
  const customPasswordResetPath = config.passwordResetUrl && config.passwordResetUrl !== "/reset-password" ? config.passwordResetUrl : null;
15
15
  const allPublicPaths = [
16
- ...noAuthPaths,
17
- ...noAuthApiPaths,
16
+ ...publicPages,
17
+ ...publicApiEndpoints,
18
18
  ...whitelistedPaths
19
19
  ];
20
20
  if (customPasswordResetPath) {
@@ -25,8 +25,8 @@ export const usePublicPaths = () => {
25
25
  all: allPublicPaths,
26
26
  // Categorized public paths
27
27
  builtIn: {
28
- pages: noAuthPaths,
29
- api: noAuthApiPaths
28
+ pages: publicPages,
29
+ api: publicApiEndpoints
30
30
  },
31
31
  whitelist: whitelistedPaths,
32
32
  customPasswordResetPath,
@@ -69,11 +69,11 @@ export const usePublicPaths = () => {
69
69
  if (path.startsWith("/_")) {
70
70
  return true;
71
71
  }
72
- if (NO_AUTH_PATHS.includes(path)) {
72
+ if (PUBLIC_PAGES.includes(path)) {
73
73
  return true;
74
74
  }
75
- const noAuthApiPaths = NO_AUTH_API_PATHS.map((p) => `${apiBasePath}${p}`);
76
- if (noAuthApiPaths.includes(path)) {
75
+ const publicApiEndpoints = PUBLIC_API_ENDPOINTS.map((endpoint) => `${endpoint.methods.join(", ")}: ${apiBasePath}${endpoint.path}`);
76
+ if (publicApiEndpoints.includes(path)) {
77
77
  return true;
78
78
  }
79
79
  if (config.passwordResetUrl && config.passwordResetUrl !== "/reset-password" && path === config.passwordResetUrl) {
@@ -83,6 +83,10 @@ export const usePublicPaths = () => {
83
83
  return true;
84
84
  }
85
85
  if (user.value) {
86
+ const autoWhitelistedApiEndpoints = AUTHENTICATED_AUTO_ACCESS_ENDPOINTS.map((endpoint) => `${endpoint.methods.join(", ")}: ${apiBasePath}${endpoint.path}`);
87
+ if (autoWhitelistedApiEndpoints.includes(path)) {
88
+ return true;
89
+ }
86
90
  return hasPermission(user.value.role, path, method, config.auth?.permissions || {});
87
91
  }
88
92
  return false;
@@ -94,11 +98,11 @@ export const usePublicPaths = () => {
94
98
  if (path.startsWith("/_")) {
95
99
  return true;
96
100
  }
97
- if (NO_AUTH_PATHS.includes(path)) {
101
+ if (PUBLIC_PAGES.includes(path)) {
98
102
  return true;
99
103
  }
100
- const noAuthApiPaths = NO_AUTH_API_PATHS.map((p) => `${apiBasePath}${p}`);
101
- if (noAuthApiPaths.includes(path)) {
104
+ const publicApiEndpoints = PUBLIC_API_ENDPOINTS.map((endpoint) => `${endpoint.methods.join(", ")}: ${apiBasePath}${endpoint.path}`);
105
+ if (publicApiEndpoints.includes(path)) {
102
106
  return true;
103
107
  }
104
108
  if (config.passwordResetUrl && config.passwordResetUrl !== "/reset-password" && path === config.passwordResetUrl) {
@@ -1,2 +1,9 @@
1
- export declare const NO_AUTH_PATHS: string[];
2
- export declare const NO_AUTH_API_PATHS: string[];
1
+ export declare const PUBLIC_PAGES: string[];
2
+ export declare const PUBLIC_API_ENDPOINTS: {
3
+ path: string;
4
+ methods: string[];
5
+ }[];
6
+ export declare const AUTHENTICATED_AUTO_ACCESS_ENDPOINTS: {
7
+ path: string;
8
+ methods: string[];
9
+ }[];
@@ -1,2 +1,11 @@
1
- export const NO_AUTH_PATHS = ["/login", "/reset-password"];
2
- export const NO_AUTH_API_PATHS = ["/session", "/password/forgot", "/password/reset"];
1
+ export const PUBLIC_PAGES = ["/login", "/reset-password"];
2
+ export const PUBLIC_API_ENDPOINTS = [
3
+ { path: "/password/forgot", methods: ["POST"] },
4
+ { path: "/password/reset", methods: ["POST"] },
5
+ { path: "/session", methods: ["POST", "DELETE"] }
6
+ // Login and logout endpoints
7
+ ];
8
+ export const AUTHENTICATED_AUTO_ACCESS_ENDPOINTS = [
9
+ { path: "/me", methods: ["GET", "PATCH"] },
10
+ { path: "/password", methods: ["PATCH"] }
11
+ ];
@@ -79,6 +79,24 @@ export const en = {
79
79
  numbers: "Contains number",
80
80
  specialChars: "Contains special character"
81
81
  },
82
+ errors: {
83
+ minLength: "Password must be at least {0} characters long",
84
+ uppercase: "Password must contain at least one uppercase letter",
85
+ lowercase: "Password must contain at least one lowercase letter",
86
+ numbers: "Password must contain at least one number",
87
+ specialChars: "Password must contain at least one special character",
88
+ common: "Password is too common. Please choose a more unique password"
89
+ },
90
+ hints: {
91
+ minLength: "Use at least {0} characters",
92
+ length12: "Use 12 or more characters for extra security",
93
+ uppercase: "Add an uppercase letter (A-Z)",
94
+ lowercase: "Add a lowercase letter (a-z)",
95
+ numbers: "Add a number (0-9)",
96
+ specialChars: "Add a special character (e.g. !@#$%)",
97
+ common: "Avoid common passwords or names",
98
+ mix: "Use a mix of uppercase, lowercase, numbers, and special characters"
99
+ },
82
100
  hintsTitle: "How to make your password stronger?"
83
101
  },
84
102
  profile: {
@@ -65,6 +65,24 @@ const huBase = {
65
65
  lowercase: "Tartalmaz kisbet\u0171t",
66
66
  numbers: "Tartalmaz sz\xE1mot",
67
67
  specialChars: "Tartalmaz speci\xE1lis karaktert"
68
+ },
69
+ errors: {
70
+ minLength: "A jelsz\xF3nak legal\xE1bb {0} karakter hossz\xFAnak kell lennie",
71
+ uppercase: "A jelsz\xF3nak tartalmaznia kell legal\xE1bb egy nagybet\u0171t",
72
+ lowercase: "A jelsz\xF3nak tartalmaznia kell legal\xE1bb egy kisbet\u0171t",
73
+ numbers: "A jelsz\xF3nak tartalmaznia kell legal\xE1bb egy sz\xE1mot",
74
+ specialChars: "A jelsz\xF3nak tartalmaznia kell legal\xE1bb egy speci\xE1lis karaktert",
75
+ common: "Ez a jelsz\xF3 t\xFAl gyakori. K\xE9rj\xFCk, v\xE1lassz egyedibbet"
76
+ },
77
+ hints: {
78
+ minLength: "Haszn\xE1lj legal\xE1bb {0} karaktert",
79
+ length12: "Haszn\xE1lj 12 vagy t\xF6bb karaktert a nagyobb biztons\xE1g \xE9rdek\xE9ben",
80
+ uppercase: "Adj hozz\xE1 egy nagybet\u0171t (A-Z)",
81
+ lowercase: "Adj hozz\xE1 egy kisbet\u0171t (a-z)",
82
+ numbers: "Adj hozz\xE1 egy sz\xE1mot (0-9)",
83
+ specialChars: "Adj hozz\xE1 egy speci\xE1lis karaktert (pl. !@#$%)",
84
+ common: "Ker\xFCld a gyakori jelszavakat \xE9s neveket",
85
+ mix: "Haszn\xE1lj vegyesen nagybet\u0171ket, kisbet\u0171ket, sz\xE1mokat \xE9s speci\xE1lis karaktereket"
68
86
  }
69
87
  },
70
88
  profile: {
@@ -1,20 +1,20 @@
1
1
  import { defineNuxtRouteMiddleware, navigateTo, useRuntimeConfig } from "#app";
2
2
  import { useAuthentication } from "../composables/useAuthentication.js";
3
3
  import { hasPermission, isWhitelisted } from "../utils/permissions.js";
4
- import { NO_AUTH_PATHS, NO_AUTH_API_PATHS } from "../constants.js";
4
+ import { PUBLIC_PAGES, AUTHENTICATED_AUTO_ACCESS_ENDPOINTS } from "../constants.js";
5
5
  export default defineNuxtRouteMiddleware(async (to) => {
6
6
  const { public: { nuxtUsers: publicNuxtUsers } } = useRuntimeConfig();
7
7
  const publicOptions = publicNuxtUsers;
8
8
  const base = publicOptions.apiBasePath || "/api/nuxt-users";
9
- if (NO_AUTH_PATHS.includes(to.path)) {
9
+ if (PUBLIC_PAGES.includes(to.path)) {
10
10
  console.log(`[Nuxt Users] client.middleware.auth.global: ${to.path}`);
11
11
  return;
12
12
  }
13
- const openApiPaths = NO_AUTH_API_PATHS.map((path) => `${base}${path}`);
13
+ const openApiPaths = AUTHENTICATED_AUTO_ACCESS_ENDPOINTS.map((endpoint) => `${endpoint.methods.join(", ")}: ${base}${endpoint.path}`);
14
14
  if (openApiPaths.includes(to.path)) {
15
15
  return;
16
16
  }
17
- if (isWhitelisted(to.path, publicOptions.auth.whitelist)) {
17
+ if (isWhitelisted(to.path, publicOptions.auth?.whitelist || [])) {
18
18
  console.log(`[Nuxt Users] client.middleware.auth.global: Whitelisted: ${to.path}`);
19
19
  return;
20
20
  }
@@ -33,7 +33,7 @@ export default defineNuxtRouteMiddleware(async (to) => {
33
33
  console.log(`[Nuxt Users] client.middleware.auth.global: Unauthenticated ${to.path}, redirecting to /login`);
34
34
  return navigateTo("/login");
35
35
  }
36
- if (!user.value || !hasPermission(user.value.role, to.path, "GET", publicOptions.auth.permissions)) {
36
+ if (!user.value || !hasPermission(user.value.role, to.path, "GET", publicOptions.auth?.permissions || {})) {
37
37
  console.log(`[Nuxt Users] client.middleware.auth.global: User with role ${user.value?.role} denied access to ${to.path}`);
38
38
  return navigateTo("/login");
39
39
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Theme detection plugin
3
+ *
4
+ * Automatically applies dark theme class based on system preferences.
5
+ * Listens for changes to system theme and updates accordingly.
6
+ */
7
+ declare const _default: import("#app").Plugin<Record<string, unknown>> & import("#app").ObjectPlugin<Record<string, unknown>>;
8
+ export default _default;
@@ -0,0 +1,27 @@
1
+ import { defineNuxtPlugin } from "#app";
2
+ export default defineNuxtPlugin(() => {
3
+ if (import.meta.server) {
4
+ return;
5
+ }
6
+ const applyTheme = () => {
7
+ if (typeof window === "undefined" || typeof document === "undefined") {
8
+ return;
9
+ }
10
+ const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
11
+ if (prefersDark) {
12
+ document.documentElement.classList.add("dark");
13
+ }
14
+ if (!prefersDark) {
15
+ document.documentElement.classList.remove("dark");
16
+ }
17
+ };
18
+ applyTheme();
19
+ if (typeof window !== "undefined" && window.matchMedia) {
20
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
21
+ if (mediaQuery.addEventListener) {
22
+ mediaQuery.addEventListener("change", applyTheme);
23
+ } else if (mediaQuery.addListener) {
24
+ mediaQuery.addListener(applyTheme);
25
+ }
26
+ }
27
+ });
@@ -1,82 +1,58 @@
1
- import { defineEventHandler, getCookie, createError } from "h3";
1
+ import { defineEventHandler, getCookie } from "h3";
2
2
  import { useRuntimeConfig } from "#imports";
3
- import { getCurrentUserFromToken } from "../utils/index.js";
3
+ import { getCurrentUserFromToken, handleUnauthenticatedRequest, handleInvalidToken, handleUnauthorizedRequest } from "../utils/index.js";
4
4
  import { hasPermission, isWhitelisted } from "../../utils/permissions.js";
5
- import { NO_AUTH_PATHS, NO_AUTH_API_PATHS } from "../../constants.js";
5
+ import { PUBLIC_PAGES, PUBLIC_API_ENDPOINTS, AUTHENTICATED_AUTO_ACCESS_ENDPOINTS } from "../../constants.js";
6
6
  export default defineEventHandler(async (event) => {
7
7
  const { nuxtUsers } = useRuntimeConfig();
8
8
  const options = nuxtUsers;
9
9
  const base = options.apiBasePath || "/api/nuxt-users";
10
- const isPageOrApiRoute = !event.path.includes(".") && (event.path === "/" || event.path.startsWith("/api/") || !event.path.startsWith("/_"));
10
+ const isApiRoute = event.path.startsWith("/api/");
11
+ const isMeEndpoint = event.path === `${base}/me`;
12
+ const isPageOrApiRoute = !event.path.includes(".") && (event.path === "/" || isApiRoute || !event.path.startsWith("/_"));
11
13
  if (!isPageOrApiRoute) {
12
14
  return;
13
15
  }
14
- const noAuthPaths = [...NO_AUTH_PATHS];
16
+ const publicAuthPaths = [...PUBLIC_PAGES];
15
17
  if (options.passwordResetUrl && options.passwordResetUrl !== "/reset-password") {
16
- noAuthPaths.push(options.passwordResetUrl);
18
+ publicAuthPaths.push(options.passwordResetUrl);
17
19
  }
18
- if (noAuthPaths.includes(event.path)) {
20
+ if (publicAuthPaths.includes(event.path)) {
19
21
  console.debug(`[Nuxt Users] authorization: NO_AUTH_PATH: ${event.path}`);
20
22
  return;
21
23
  }
22
- const openApiPaths = NO_AUTH_API_PATHS.map((path) => `${base}${path}`);
23
- if (openApiPaths.includes(event.path)) {
24
- return;
24
+ for (const endpoint of PUBLIC_API_ENDPOINTS) {
25
+ if (event.path === `${base}${endpoint.path}` && endpoint.methods.includes(event.method)) {
26
+ console.debug(`[Nuxt Users] authorization: Public API endpoint ${event.path} (${event.method})`);
27
+ return;
28
+ }
25
29
  }
26
30
  if (isWhitelisted(event.path, options.auth.whitelist)) {
27
31
  console.debug(`[Nuxt Users] authorization: whitelisted: ${event.path}`);
28
32
  return;
29
33
  }
30
34
  const token = getCookie(event, "auth_token");
31
- const isMeEndpoint = event.path === `${base}/me`;
32
35
  if (!token) {
33
- if (event.path.startsWith("/api/")) {
34
- if (isMeEndpoint) {
35
- console.debug(`[Nuxt Users] authorization: ${event.path} No token found - expected for auth check`);
36
- } else {
37
- console.warn(`[Nuxt Users] authorization: ${event.path} No token found - API request rejected`);
38
- }
39
- throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
40
- }
41
- if (!event.path.startsWith("/api/")) {
42
- console.debug(`[Nuxt Users] authorization: ${event.path} No token found - letting client handle page redirect`);
43
- return;
44
- }
36
+ return handleUnauthenticatedRequest(event, isApiRoute, isMeEndpoint);
45
37
  }
46
38
  const user = await getCurrentUserFromToken(token, options);
47
39
  if (!user) {
48
- if (event.path.startsWith("/api/")) {
49
- if (isMeEndpoint) {
50
- console.debug(`[Nuxt Users] authorization: ${event.path} Invalid token - expected for auth check`);
51
- } else {
52
- console.warn(`[Nuxt Users] authorization: ${event.path} Invalid token - API request rejected`);
53
- }
54
- throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
55
- }
56
- if (!event.path.startsWith("/api/")) {
57
- console.debug(`[Nuxt Users] authorization: ${event.path} Invalid token - letting client handle page redirect`);
58
- return;
59
- }
40
+ return handleInvalidToken(event, isApiRoute, isMeEndpoint);
60
41
  }
61
42
  if (!event.context) {
62
43
  event.context = {};
63
44
  }
64
45
  event.context.nuxtUsers = event.context.nuxtUsers || {};
65
46
  event.context.nuxtUsers.user = user;
66
- if (event.path === `${base}/me`) {
67
- console.debug(`[Nuxt Users] authorization: Auto-whitelisted /me endpoint for authenticated user ${user.id}`);
68
- return;
69
- }
70
- if (!hasPermission(user.role, event.path, event.method, options.auth.permissions)) {
71
- if (event.path.startsWith("/api/")) {
72
- console.warn(`[Nuxt Users] authorization: ${event.path} User ${user.id} with role ${user.role} denied access - API request rejected`);
73
- throw createError({ statusCode: 403, statusMessage: "Forbidden" });
74
- }
75
- if (!event.path.startsWith("/api/")) {
76
- console.debug(`[Nuxt Users] authorization: ${event.path} User ${user.id} with role ${user.role} denied access - letting client handle page redirect`);
47
+ for (const endpoint of AUTHENTICATED_AUTO_ACCESS_ENDPOINTS) {
48
+ if (event.path === `${base}${endpoint.path}` && endpoint.methods.includes(event.method)) {
49
+ console.debug(`[Nuxt Users] authorization: Auto-whitelisted endpoint ${event.path} for authenticated user ${user.id}`);
77
50
  return;
78
51
  }
79
52
  }
53
+ if (!hasPermission(user.role, event.path, event.method, options.auth.permissions)) {
54
+ return handleUnauthorizedRequest(event, user, isApiRoute);
55
+ }
80
56
  console.debug(`[Nuxt Users] authorization: Authenticated request to ${event.path} for ${user.id} with role ${user.role}`);
81
57
  return;
82
58
  });
@@ -0,0 +1,23 @@
1
+ import { type H3Event } from 'h3';
2
+ import type { UserWithoutPassword } from 'nuxt-users/utils';
3
+ /**
4
+ * Handles unauthenticated requests (no token found)
5
+ * @param event - H3 event object
6
+ * @param isApiRoute - Whether this is an API route
7
+ * @param isMeEndpoint - Whether this is the /me endpoint (used for auth status checks)
8
+ */
9
+ export declare const handleUnauthenticatedRequest: (event: H3Event, isApiRoute: boolean, isMeEndpoint: boolean) => void;
10
+ /**
11
+ * Handles invalid token requests
12
+ * @param event - H3 event object
13
+ * @param isApiRoute - Whether this is an API route
14
+ * @param isMeEndpoint - Whether this is the /me endpoint (used for auth status checks)
15
+ */
16
+ export declare const handleInvalidToken: (event: H3Event, isApiRoute: boolean, isMeEndpoint: boolean) => void;
17
+ /**
18
+ * Handles unauthorized requests (user lacks required permissions)
19
+ * @param event - H3 event object
20
+ * @param user - Authenticated user object
21
+ * @param isApiRoute - Whether this is an API route
22
+ */
23
+ export declare const handleUnauthorizedRequest: (event: H3Event, user: UserWithoutPassword, isApiRoute: boolean) => void;
@@ -0,0 +1,33 @@
1
+ import { createError } from "h3";
2
+ export const handleUnauthenticatedRequest = (event, isApiRoute, isMeEndpoint) => {
3
+ if (isApiRoute) {
4
+ if (isMeEndpoint) {
5
+ console.debug(`[Nuxt Users] authorization: ${event.path} No token found - expected for auth check`);
6
+ } else {
7
+ console.warn(`[Nuxt Users] authorization: ${event.path} No token found - API request rejected`);
8
+ }
9
+ throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
10
+ }
11
+ console.debug(`[Nuxt Users] authorization: ${event.path} No token found - letting client handle page redirect`);
12
+ return;
13
+ };
14
+ export const handleInvalidToken = (event, isApiRoute, isMeEndpoint) => {
15
+ if (isApiRoute) {
16
+ if (isMeEndpoint) {
17
+ console.debug(`[Nuxt Users] authorization: ${event.path} Invalid token - expected for auth check`);
18
+ } else {
19
+ console.warn(`[Nuxt Users] authorization: ${event.path} Invalid token - API request rejected`);
20
+ }
21
+ throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
22
+ }
23
+ console.debug(`[Nuxt Users] authorization: ${event.path} Invalid token - letting client handle page redirect`);
24
+ return;
25
+ };
26
+ export const handleUnauthorizedRequest = (event, user, isApiRoute) => {
27
+ if (isApiRoute) {
28
+ console.warn(`[Nuxt Users] authorization: ${event.method}: ${event.path} User ${user.id} with role ${user.role} denied access - API request rejected`);
29
+ throw createError({ statusCode: 403, statusMessage: "Forbidden" });
30
+ }
31
+ console.debug(`[Nuxt Users] authorization: ${event.path} User ${user.id} with role ${user.role} denied access - letting client handle page redirect`);
32
+ return;
33
+ };
@@ -7,3 +7,4 @@ export { createPasswordResetTokensTable } from './create-password-reset-tokens-t
7
7
  export { createMigrationsTable } from './create-migrations-table.js';
8
8
  export { runMigrations, getAppliedMigrations } from './migrate.js';
9
9
  export { createGoogleOAuth2Client, getGoogleAuthUrl, getGoogleUserFromCode, findOrCreateGoogleUser, createAuthTokenForUser, generateSecurePassword } from './google-oauth.js';
10
+ export { handleUnauthenticatedRequest, handleInvalidToken, handleUnauthorizedRequest } from './authorization.js';
@@ -7,3 +7,4 @@ export { createPasswordResetTokensTable } from "./create-password-reset-tokens-t
7
7
  export { createMigrationsTable } from "./create-migrations-table.js";
8
8
  export { runMigrations, getAppliedMigrations } from "./migrate.js";
9
9
  export { createGoogleOAuth2Client, getGoogleAuthUrl, getGoogleUserFromCode, findOrCreateGoogleUser, createAuthTokenForUser, generateSecurePassword } from "./google-oauth.js";
10
+ export { handleUnauthenticatedRequest, handleInvalidToken, handleUnauthorizedRequest } from "./authorization.js";
@@ -67,46 +67,46 @@ const validatePassword = (password, options = {}) => {
67
67
  const hints = [];
68
68
  let score = 0;
69
69
  if (password.length < config.minLength) {
70
- errors.push(`Password must be at least ${config.minLength} characters long`);
71
- hints.push(`Use at least ${config.minLength} characters`);
70
+ errors.push(`passwordStrength.errors.minLength|${config.minLength}`);
71
+ hints.push(`passwordStrength.hints.minLength|${config.minLength}`);
72
72
  }
73
73
  if (password.length >= config.minLength) {
74
74
  score += 20;
75
75
  if (password.length < 12) {
76
- hints.push("Use 12 or more characters for extra security");
76
+ hints.push("passwordStrength.hints.length12");
77
77
  }
78
78
  }
79
79
  if (config.requireUppercase && !/[A-Z]/.test(password)) {
80
- errors.push("Password must contain at least one uppercase letter");
81
- hints.push("Add an uppercase letter (A-Z)");
80
+ errors.push("passwordStrength.errors.uppercase");
81
+ hints.push("passwordStrength.hints.uppercase");
82
82
  }
83
83
  if (/[A-Z]/.test(password)) {
84
84
  score += 15;
85
85
  }
86
86
  if (config.requireLowercase && !/[a-z]/.test(password)) {
87
- errors.push("Password must contain at least one lowercase letter");
88
- hints.push("Add a lowercase letter (a-z)");
87
+ errors.push("passwordStrength.errors.lowercase");
88
+ hints.push("passwordStrength.hints.lowercase");
89
89
  }
90
90
  if (/[a-z]/.test(password)) {
91
91
  score += 15;
92
92
  }
93
93
  if (config.requireNumbers && !/\d/.test(password)) {
94
- errors.push("Password must contain at least one number");
95
- hints.push("Add a number (0-9)");
94
+ errors.push("passwordStrength.errors.numbers");
95
+ hints.push("passwordStrength.hints.numbers");
96
96
  }
97
97
  if (/\d/.test(password)) {
98
98
  score += 15;
99
99
  }
100
100
  if (config.requireSpecialChars && !/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password)) {
101
- errors.push("Password must contain at least one special character");
102
- hints.push("Add a special character (e.g. !@#$%)");
101
+ errors.push("passwordStrength.errors.specialChars");
102
+ hints.push("passwordStrength.hints.specialChars");
103
103
  }
104
104
  if (/[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(password)) {
105
105
  score += 15;
106
106
  }
107
107
  if (config.preventCommonPasswords && COMMON_PASSWORDS.includes(password.toLowerCase())) {
108
- errors.push("Password is too common. Please choose a more unique password");
109
- hints.push("Avoid common passwords or names");
108
+ errors.push("passwordStrength.errors.common");
109
+ hints.push("passwordStrength.hints.common");
110
110
  }
111
111
  if (password.length >= 12) {
112
112
  score += 10;
@@ -124,7 +124,7 @@ const validatePassword = (password, options = {}) => {
124
124
  }
125
125
  if (complexityTypes >= 3 && complexityTypes < 4) {
126
126
  score += 5;
127
- hints.push("Use a mix of uppercase, lowercase, numbers, and special characters");
127
+ hints.push("passwordStrength.hints.mix");
128
128
  }
129
129
  let strength = "weak";
130
130
  if (score >= 80) {
package/dist/utils.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { a as getPasswordStrengthColor, b as getPasswordStrengthText, g as getPasswordValidationOptions, v as validatePassword } from './shared/nuxt-users.DfZ7bc_P.mjs';
1
+ export { a as getPasswordStrengthColor, b as getPasswordStrengthText, g as getPasswordValidationOptions, v as validatePassword } from './shared/nuxt-users.Cvjgn5KJ.mjs';
2
2
  export { defaultLocaleMessages, en, hu, huFormal } from '../dist/runtime/locales/index.js';
3
3
  export { getNestedValue, getTranslation } from '../dist/runtime/utils/locale.js';
4
4
  export { deepMerge } from '../dist/runtime/utils/deep-merge.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-users",
3
- "version": "1.40.1",
3
+ "version": "1.41.0",
4
4
  "description": "A comprehensive user management module for Nuxt 3 and Nuxt 4 applications with authentication, authorization, database support, and CLI tools",
5
5
  "keywords": [
6
6
  "nuxt",
@@ -64,9 +64,9 @@
64
64
  "postbuild": "node scripts/post-build.js",
65
65
  "check:translations": "tsx scripts/check-translations.ts",
66
66
  "check:locale-consistency": "tsx scripts/check-locale-consistency.ts",
67
- "check:auth": "npm whoami --registry=https://registry.npmjs.org/ || (echo '❌ Not authenticated with npm. Please run: npm login' && exit 1)",
67
+ "check:npm_token": "dotenv -- sh -c 'if [ -z \"$NPM_TOKEN\" ]; then echo \"❌ NPM_TOKEN environment variable is not set in .env file. Please set it to your npm token.\"; exit 1; fi'",
68
68
  "prerelease": "node -v | grep -q 'v22' || (echo 'Please use Node.js v22 for deployment' && exit 1)",
69
- "release": "yarn check:auth && yarn lint && yarn check:translations && yarn check:locale-consistency && yarn test && yarn prepack && changelogen --release && npm publish && git push --follow-tags",
69
+ "release": "yarn check:npm_token && yarn lint && yarn check:translations && yarn check:locale-consistency && yarn test && yarn prepack && dotenv -- changelogen --release && dotenv -- npm publish --registry https://registry.npmjs.org/ && git push --follow-tags",
70
70
  "lint": "eslint .",
71
71
  "lint:fix": "eslint . --fix",
72
72
  "pretest": "node -v | grep -q 'v22' || (echo 'Please use Node.js v22 for testing' && exit 1)",
@@ -130,6 +130,7 @@
130
130
  "@types/nodemailer": "^6.4.17",
131
131
  "@types/pg": "^8.15.4",
132
132
  "changelogen": "^0.6.2",
133
+ "dotenv-cli": "^11.0.0",
133
134
  "eslint": "^9.30.1",
134
135
  "nuxt": "^4.0.0",
135
136
  "tsx": "^4.20.3",