nuxt-users 1.6.5 → 1.6.7
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/CHANGELOG.md +63 -0
- package/dist/cli.mjs +1 -1
- package/dist/module.d.mts +4 -70
- package/dist/module.d.ts +4 -70
- package/dist/module.json +1 -1
- package/dist/module.mjs +28 -27
- package/dist/runtime/components/LoginForm.vue +6 -3
- package/dist/runtime/components/LoginForm.vue.d.ts +4 -4
- package/dist/runtime/components/LogoutLink.vue +21 -7
- package/dist/runtime/components/LogoutLink.vue.d.ts +22 -5
- package/dist/runtime/components/PasswordStrengthIndicator.vue +38 -27
- package/dist/runtime/components/ProfileInfo.vue +33 -0
- package/dist/runtime/components/ResetPasswordForm.vue +89 -96
- package/dist/runtime/components/ResetPasswordForm.vue.d.ts +15 -1
- package/dist/runtime/composables/useAuth.d.ts +2 -2
- package/dist/runtime/composables/useAuth.js +2 -2
- package/dist/runtime/composables/usePasswordValidation.d.ts +1 -1
- package/dist/runtime/middleware/auth.client.js +4 -4
- package/dist/runtime/server/api/{login.post.js → auth/login.post.js} +5 -3
- package/dist/runtime/server/api/{logout.get.js → auth/logout.get.js} +1 -1
- package/dist/runtime/server/api/{update-password.post.js → auth/update-password.post.js} +2 -2
- package/dist/runtime/server/api/user/profile.get.d.ts +4 -0
- package/dist/runtime/server/api/{profile.get.js → user/profile.get.js} +2 -2
- package/dist/runtime/server/middleware/auth.server.js +3 -2
- package/dist/runtime/server/tasks/cleanup-tokens.d.ts +2 -0
- package/dist/runtime/server/tasks/cleanup-tokens.js +30 -0
- package/dist/runtime/server/utils/index.d.ts +1 -1
- package/dist/runtime/server/utils/index.js +1 -1
- package/dist/runtime/server/utils/user.d.ts +20 -0
- package/dist/runtime/server/utils/user.js +45 -1
- package/package.json +1 -1
- package/dist/runtime/components/ForgotPasswordForm.vue +0 -77
- package/dist/runtime/components/ProfileForm.vue +0 -240
- package/dist/runtime/components/ProfileForm.vue.d.ts +0 -20
- package/dist/runtime/server/api/profile.get.d.ts +0 -2
- /package/dist/runtime/components/{ForgotPasswordForm.vue.d.ts → ProfileInfo.vue.d.ts} +0 -0
- /package/dist/runtime/server/api/{login.post.d.ts → auth/login.post.d.ts} +0 -0
- /package/dist/runtime/server/api/{logout.get.d.ts → auth/logout.get.d.ts} +0 -0
- /package/dist/runtime/server/api/{update-password.post.d.ts → auth/update-password.post.d.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,69 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v1.6.7
|
|
5
|
+
|
|
6
|
+
[compare changes](https://github.com/rrd108/nuxt-users/compare/v1.6.6...v1.6.7)
|
|
7
|
+
|
|
8
|
+
### 🩹 Fixes
|
|
9
|
+
|
|
10
|
+
- Authentication middleware logic ([18a3bb5](https://github.com/rrd108/nuxt-users/commit/18a3bb5))
|
|
11
|
+
- Password validation and UI ([07b44b3](https://github.com/rrd108/nuxt-users/commit/07b44b3))
|
|
12
|
+
- Type errors ([c403645](https://github.com/rrd108/nuxt-users/commit/c403645))
|
|
13
|
+
|
|
14
|
+
### 💅 Refactors
|
|
15
|
+
|
|
16
|
+
- API routes ([3c7603f](https://github.com/rrd108/nuxt-users/commit/3c7603f))
|
|
17
|
+
- Authentication components and logic ([4da544f](https://github.com/rrd108/nuxt-users/commit/4da544f))
|
|
18
|
+
- Token cleanup task and middleware ([d47a4a5](https://github.com/rrd108/nuxt-users/commit/d47a4a5))
|
|
19
|
+
|
|
20
|
+
### 🏡 Chore
|
|
21
|
+
|
|
22
|
+
- **release:** V1.6.6 ([7b37694](https://github.com/rrd108/nuxt-users/commit/7b37694))
|
|
23
|
+
|
|
24
|
+
### ✅ Tests
|
|
25
|
+
|
|
26
|
+
- Fix mock ([791dc94](https://github.com/rrd108/nuxt-users/commit/791dc94))
|
|
27
|
+
- Adds unit tests for auth API endpoints ([d844630](https://github.com/rrd108/nuxt-users/commit/d844630))
|
|
28
|
+
- Remove unused test ([3dc23ad](https://github.com/rrd108/nuxt-users/commit/3dc23ad))
|
|
29
|
+
- Fix response ([db3b03c](https://github.com/rrd108/nuxt-users/commit/db3b03c))
|
|
30
|
+
- Fix mysql field type ([2faf0c6](https://github.com/rrd108/nuxt-users/commit/2faf0c6))
|
|
31
|
+
|
|
32
|
+
### ❤️ Contributors
|
|
33
|
+
|
|
34
|
+
- Rrd108 ([@rrd108](https://github.com/rrd108))
|
|
35
|
+
|
|
36
|
+
## v1.6.6
|
|
37
|
+
|
|
38
|
+
[compare changes](https://github.com/rrd108/nuxt-users/compare/v1.6.5...v1.6.6)
|
|
39
|
+
|
|
40
|
+
### 🩹 Fixes
|
|
41
|
+
|
|
42
|
+
- Authentication middleware ([c7de8a4](https://github.com/rrd108/nuxt-users/commit/c7de8a4))
|
|
43
|
+
- Lint and type errors ([dd6ee40](https://github.com/rrd108/nuxt-users/commit/dd6ee40))
|
|
44
|
+
- Type errors ([65c81fb](https://github.com/rrd108/nuxt-users/commit/65c81fb))
|
|
45
|
+
- **cli:** Updates CLI version dynamically ([1cca13a](https://github.com/rrd108/nuxt-users/commit/1cca13a))
|
|
46
|
+
- Authentication middleware logic ([18a3bb5](https://github.com/rrd108/nuxt-users/commit/18a3bb5))
|
|
47
|
+
- Password validation and UI ([07b44b3](https://github.com/rrd108/nuxt-users/commit/07b44b3))
|
|
48
|
+
|
|
49
|
+
### 💅 Refactors
|
|
50
|
+
|
|
51
|
+
- API routes ([3c7603f](https://github.com/rrd108/nuxt-users/commit/3c7603f))
|
|
52
|
+
- Authentication components and logic ([4da544f](https://github.com/rrd108/nuxt-users/commit/4da544f))
|
|
53
|
+
|
|
54
|
+
### 🏡 Chore
|
|
55
|
+
|
|
56
|
+
- **release:** V1.6.5 ([f35e526](https://github.com/rrd108/nuxt-users/commit/f35e526))
|
|
57
|
+
|
|
58
|
+
### ✅ Tests
|
|
59
|
+
|
|
60
|
+
- Fix mock ([791dc94](https://github.com/rrd108/nuxt-users/commit/791dc94))
|
|
61
|
+
- Adds unit tests for auth API endpoints ([d844630](https://github.com/rrd108/nuxt-users/commit/d844630))
|
|
62
|
+
|
|
63
|
+
### ❤️ Contributors
|
|
64
|
+
|
|
65
|
+
- Rrd108 ([@rrd108](https://github.com/rrd108))
|
|
66
|
+
|
|
4
67
|
## v1.6.5
|
|
5
68
|
|
|
6
69
|
[compare changes](https://github.com/rrd108/nuxt-users/compare/v1.6.4...v1.6.5)
|
package/dist/cli.mjs
CHANGED
package/dist/module.d.mts
CHANGED
|
@@ -9,76 +9,7 @@ type DatabaseConfig = {
|
|
|
9
9
|
password?: string;
|
|
10
10
|
database?: string;
|
|
11
11
|
};
|
|
12
|
-
interface
|
|
13
|
-
connector?: {
|
|
14
|
-
name: DatabaseType;
|
|
15
|
-
options: DatabaseConfig;
|
|
16
|
-
};
|
|
17
|
-
tables?: {
|
|
18
|
-
migrations?: string;
|
|
19
|
-
users?: string;
|
|
20
|
-
personalAccessTokens?: string;
|
|
21
|
-
passwordResetTokens?: string;
|
|
22
|
-
};
|
|
23
|
-
/**
|
|
24
|
-
* Mailer configuration options for sending emails (e.g., password resets)
|
|
25
|
-
* Uses nodemailer
|
|
26
|
-
*/
|
|
27
|
-
mailer?: MailerOptions;
|
|
28
|
-
/**
|
|
29
|
-
* Base URL for password reset links
|
|
30
|
-
* @default 'http://localhost:3000' // Example, will be set in module defaults
|
|
31
|
-
*/
|
|
32
|
-
passwordResetBaseUrl?: string;
|
|
33
|
-
/**
|
|
34
|
-
* Skip database checks during module setup to prevent hanging
|
|
35
|
-
* @default false
|
|
36
|
-
*/
|
|
37
|
-
auth?: {
|
|
38
|
-
/**
|
|
39
|
-
* Whitelisted routes that do not require authentication
|
|
40
|
-
* @default ['/login']
|
|
41
|
-
* @example ['/login', '/register']
|
|
42
|
-
*/
|
|
43
|
-
whitelist?: string[];
|
|
44
|
-
};
|
|
45
|
-
/**
|
|
46
|
-
* Password validation configuration
|
|
47
|
-
*/
|
|
48
|
-
passwordValidation?: {
|
|
49
|
-
/**
|
|
50
|
-
* Minimum password length
|
|
51
|
-
* @default 8
|
|
52
|
-
*/
|
|
53
|
-
minLength?: number;
|
|
54
|
-
/**
|
|
55
|
-
* Require uppercase letters
|
|
56
|
-
* @default true
|
|
57
|
-
*/
|
|
58
|
-
requireUppercase?: boolean;
|
|
59
|
-
/**
|
|
60
|
-
* Require lowercase letters
|
|
61
|
-
* @default true
|
|
62
|
-
*/
|
|
63
|
-
requireLowercase?: boolean;
|
|
64
|
-
/**
|
|
65
|
-
* Require numbers
|
|
66
|
-
* @default true
|
|
67
|
-
*/
|
|
68
|
-
requireNumbers?: boolean;
|
|
69
|
-
/**
|
|
70
|
-
* Require special characters
|
|
71
|
-
* @default true
|
|
72
|
-
*/
|
|
73
|
-
requireSpecialChars?: boolean;
|
|
74
|
-
/**
|
|
75
|
-
* Prevent common passwords
|
|
76
|
-
* @default true
|
|
77
|
-
*/
|
|
78
|
-
preventCommonPasswords?: boolean;
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
interface ModuleOptions extends Omit<RuntimeModuleOptions, 'tables' | 'auth' | 'passwordValidation'> {
|
|
12
|
+
interface ModuleOptions {
|
|
82
13
|
connector: {
|
|
83
14
|
name: DatabaseType;
|
|
84
15
|
options: DatabaseConfig;
|
|
@@ -89,8 +20,11 @@ interface ModuleOptions extends Omit<RuntimeModuleOptions, 'tables' | 'auth' | '
|
|
|
89
20
|
personalAccessTokens: string;
|
|
90
21
|
passwordResetTokens: string;
|
|
91
22
|
};
|
|
23
|
+
mailer?: MailerOptions;
|
|
24
|
+
passwordResetBaseUrl?: string;
|
|
92
25
|
auth: {
|
|
93
26
|
whitelist: string[];
|
|
27
|
+
tokenExpiration: number;
|
|
94
28
|
};
|
|
95
29
|
passwordValidation: {
|
|
96
30
|
minLength: number;
|
package/dist/module.d.ts
CHANGED
|
@@ -9,76 +9,7 @@ type DatabaseConfig = {
|
|
|
9
9
|
password?: string;
|
|
10
10
|
database?: string;
|
|
11
11
|
};
|
|
12
|
-
interface
|
|
13
|
-
connector?: {
|
|
14
|
-
name: DatabaseType;
|
|
15
|
-
options: DatabaseConfig;
|
|
16
|
-
};
|
|
17
|
-
tables?: {
|
|
18
|
-
migrations?: string;
|
|
19
|
-
users?: string;
|
|
20
|
-
personalAccessTokens?: string;
|
|
21
|
-
passwordResetTokens?: string;
|
|
22
|
-
};
|
|
23
|
-
/**
|
|
24
|
-
* Mailer configuration options for sending emails (e.g., password resets)
|
|
25
|
-
* Uses nodemailer
|
|
26
|
-
*/
|
|
27
|
-
mailer?: MailerOptions;
|
|
28
|
-
/**
|
|
29
|
-
* Base URL for password reset links
|
|
30
|
-
* @default 'http://localhost:3000' // Example, will be set in module defaults
|
|
31
|
-
*/
|
|
32
|
-
passwordResetBaseUrl?: string;
|
|
33
|
-
/**
|
|
34
|
-
* Skip database checks during module setup to prevent hanging
|
|
35
|
-
* @default false
|
|
36
|
-
*/
|
|
37
|
-
auth?: {
|
|
38
|
-
/**
|
|
39
|
-
* Whitelisted routes that do not require authentication
|
|
40
|
-
* @default ['/login']
|
|
41
|
-
* @example ['/login', '/register']
|
|
42
|
-
*/
|
|
43
|
-
whitelist?: string[];
|
|
44
|
-
};
|
|
45
|
-
/**
|
|
46
|
-
* Password validation configuration
|
|
47
|
-
*/
|
|
48
|
-
passwordValidation?: {
|
|
49
|
-
/**
|
|
50
|
-
* Minimum password length
|
|
51
|
-
* @default 8
|
|
52
|
-
*/
|
|
53
|
-
minLength?: number;
|
|
54
|
-
/**
|
|
55
|
-
* Require uppercase letters
|
|
56
|
-
* @default true
|
|
57
|
-
*/
|
|
58
|
-
requireUppercase?: boolean;
|
|
59
|
-
/**
|
|
60
|
-
* Require lowercase letters
|
|
61
|
-
* @default true
|
|
62
|
-
*/
|
|
63
|
-
requireLowercase?: boolean;
|
|
64
|
-
/**
|
|
65
|
-
* Require numbers
|
|
66
|
-
* @default true
|
|
67
|
-
*/
|
|
68
|
-
requireNumbers?: boolean;
|
|
69
|
-
/**
|
|
70
|
-
* Require special characters
|
|
71
|
-
* @default true
|
|
72
|
-
*/
|
|
73
|
-
requireSpecialChars?: boolean;
|
|
74
|
-
/**
|
|
75
|
-
* Prevent common passwords
|
|
76
|
-
* @default true
|
|
77
|
-
*/
|
|
78
|
-
preventCommonPasswords?: boolean;
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
interface ModuleOptions extends Omit<RuntimeModuleOptions, 'tables' | 'auth' | 'passwordValidation'> {
|
|
12
|
+
interface ModuleOptions {
|
|
82
13
|
connector: {
|
|
83
14
|
name: DatabaseType;
|
|
84
15
|
options: DatabaseConfig;
|
|
@@ -89,8 +20,11 @@ interface ModuleOptions extends Omit<RuntimeModuleOptions, 'tables' | 'auth' | '
|
|
|
89
20
|
personalAccessTokens: string;
|
|
90
21
|
passwordResetTokens: string;
|
|
91
22
|
};
|
|
23
|
+
mailer?: MailerOptions;
|
|
24
|
+
passwordResetBaseUrl?: string;
|
|
92
25
|
auth: {
|
|
93
26
|
whitelist: string[];
|
|
27
|
+
tokenExpiration: number;
|
|
94
28
|
};
|
|
95
29
|
passwordValidation: {
|
|
96
30
|
minLength: number;
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineNuxtModule, createResolver, addPlugin, addImportsDir,
|
|
1
|
+
import { defineNuxtModule, createResolver, addPlugin, addImportsDir, addRouteMiddleware, addServerHandler, addComponent } from '@nuxt/kit';
|
|
2
2
|
import { defu } from 'defu';
|
|
3
3
|
|
|
4
4
|
const defaultOptions = {
|
|
@@ -31,7 +31,9 @@ const defaultOptions = {
|
|
|
31
31
|
},
|
|
32
32
|
passwordResetBaseUrl: "http://localhost:3000",
|
|
33
33
|
auth: {
|
|
34
|
-
whitelist: ["/login"]
|
|
34
|
+
whitelist: ["/login"],
|
|
35
|
+
tokenExpiration: 24 * 60
|
|
36
|
+
// 24 hours
|
|
35
37
|
},
|
|
36
38
|
passwordValidation: {
|
|
37
39
|
minLength: 8,
|
|
@@ -61,7 +63,8 @@ const module = defineNuxtModule({
|
|
|
61
63
|
passwordResetTokens: options.tables?.passwordResetTokens || defaultOptions.tables.passwordResetTokens
|
|
62
64
|
},
|
|
63
65
|
auth: {
|
|
64
|
-
whitelist: [...defaultOptions.auth?.whitelist || [], ...options.auth?.whitelist || []]
|
|
66
|
+
whitelist: [...defaultOptions.auth?.whitelist || [], ...options.auth?.whitelist || []],
|
|
67
|
+
tokenExpiration: options.auth?.tokenExpiration || defaultOptions.auth.tokenExpiration
|
|
65
68
|
}
|
|
66
69
|
};
|
|
67
70
|
nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {};
|
|
@@ -83,64 +86,62 @@ const module = defineNuxtModule({
|
|
|
83
86
|
mode: "server"
|
|
84
87
|
});
|
|
85
88
|
addImportsDir(resolver.resolve("./runtime/composables"));
|
|
86
|
-
addServerHandler({
|
|
87
|
-
middleware: true,
|
|
88
|
-
handler: resolver.resolve("./runtime/server/middleware/auth.server")
|
|
89
|
-
});
|
|
90
89
|
addRouteMiddleware({
|
|
91
90
|
name: "auth.client",
|
|
92
91
|
path: resolver.resolve("./runtime/middleware/auth.client"),
|
|
93
92
|
global: true
|
|
94
93
|
});
|
|
95
94
|
addServerHandler({
|
|
96
|
-
route: "/api/login",
|
|
95
|
+
route: "/api/auth/login",
|
|
97
96
|
method: "post",
|
|
98
|
-
handler: resolver.resolve("./runtime/server/api/login.post")
|
|
97
|
+
handler: resolver.resolve("./runtime/server/api/auth/login.post")
|
|
99
98
|
});
|
|
100
99
|
addServerHandler({
|
|
101
|
-
route: "/api/forgot-password",
|
|
100
|
+
route: "/api/auth/forgot-password",
|
|
102
101
|
method: "post",
|
|
103
102
|
handler: resolver.resolve("./runtime/server/api/auth/forgot-password.post")
|
|
104
103
|
});
|
|
105
104
|
addServerHandler({
|
|
106
|
-
route: "/api/reset-password",
|
|
105
|
+
route: "/api/auth/reset-password",
|
|
107
106
|
method: "post",
|
|
108
107
|
handler: resolver.resolve("./runtime/server/api/auth/reset-password.post")
|
|
109
108
|
});
|
|
110
109
|
addServerHandler({
|
|
111
|
-
route: "/api/logout",
|
|
110
|
+
route: "/api/auth/logout",
|
|
112
111
|
method: "get",
|
|
113
|
-
handler: resolver.resolve("./runtime/server/api/logout.get")
|
|
112
|
+
handler: resolver.resolve("./runtime/server/api/auth/logout.get")
|
|
114
113
|
});
|
|
115
114
|
addServerHandler({
|
|
116
|
-
route: "/api/profile",
|
|
115
|
+
route: "/api/user/profile",
|
|
117
116
|
method: "get",
|
|
118
|
-
handler: resolver.resolve("./runtime/server/api/profile.get")
|
|
117
|
+
handler: resolver.resolve("./runtime/server/api/user/profile.get")
|
|
119
118
|
});
|
|
120
119
|
addServerHandler({
|
|
121
|
-
route: "/api/update-password",
|
|
120
|
+
route: "/api/auth/update-password",
|
|
122
121
|
method: "post",
|
|
123
|
-
handler: resolver.resolve("./runtime/server/api/update-password.post")
|
|
122
|
+
handler: resolver.resolve("./runtime/server/api/auth/update-password.post")
|
|
123
|
+
});
|
|
124
|
+
nuxt.hook("nitro:config", async (nitroConfig) => {
|
|
125
|
+
nitroConfig.experimental = nitroConfig.experimental || {};
|
|
126
|
+
nitroConfig.experimental.tasks = true;
|
|
127
|
+
nitroConfig.scanDirs = nitroConfig.scanDirs || [];
|
|
128
|
+
nitroConfig.scanDirs.push(resolver.resolve("./runtime/server/tasks"));
|
|
124
129
|
});
|
|
125
130
|
addComponent({
|
|
126
131
|
name: "LoginForm",
|
|
127
132
|
filePath: resolver.resolve("./runtime/components/LoginForm.vue")
|
|
128
133
|
});
|
|
129
|
-
addComponent({
|
|
130
|
-
name: "ForgotPasswordForm",
|
|
131
|
-
filePath: resolver.resolve("./runtime/components/ForgotPasswordForm.vue")
|
|
132
|
-
});
|
|
133
|
-
addComponent({
|
|
134
|
-
name: "ResetPasswordForm",
|
|
135
|
-
filePath: resolver.resolve("./runtime/components/ResetPasswordForm.vue")
|
|
136
|
-
});
|
|
137
134
|
addComponent({
|
|
138
135
|
name: "LogoutLink",
|
|
139
136
|
filePath: resolver.resolve("./runtime/components/LogoutLink.vue")
|
|
140
137
|
});
|
|
141
138
|
addComponent({
|
|
142
|
-
name: "
|
|
143
|
-
filePath: resolver.resolve("./runtime/components/
|
|
139
|
+
name: "ProfileInfo",
|
|
140
|
+
filePath: resolver.resolve("./runtime/components/ProfileInfo.vue")
|
|
141
|
+
});
|
|
142
|
+
addComponent({
|
|
143
|
+
name: "ResetPasswordForm",
|
|
144
|
+
filePath: resolver.resolve("./runtime/components/ResetPasswordForm.vue")
|
|
144
145
|
});
|
|
145
146
|
nuxt.options.css = nuxt.options.css || [];
|
|
146
147
|
nuxt.options.css.push(resolver.resolve("./runtime/assets/colors.css"));
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref } from "vue";
|
|
3
3
|
import { navigateTo } from "#app";
|
|
4
|
+
import { useRuntimeConfig } from "#imports";
|
|
5
|
+
const { public: { nuxtUsers } } = useRuntimeConfig();
|
|
6
|
+
const { passwordValidation } = nuxtUsers;
|
|
4
7
|
const props = defineProps({
|
|
5
|
-
apiEndpoint: { type: String, required: false, default: "/api/login" },
|
|
8
|
+
apiEndpoint: { type: String, required: false, default: "/api/auth/login" },
|
|
6
9
|
redirectTo: { type: String, required: false, default: "/" },
|
|
7
10
|
forgotPasswordEndpoint: { type: String, required: false, default: "/api/auth/forgot-password" }
|
|
8
11
|
});
|
|
@@ -108,7 +111,7 @@ const handleForgotPassword = async () => {
|
|
|
108
111
|
name="password"
|
|
109
112
|
placeholder="Enter your password"
|
|
110
113
|
required
|
|
111
|
-
minlength="
|
|
114
|
+
:minlength="passwordValidation.minLength"
|
|
112
115
|
>
|
|
113
116
|
</div>
|
|
114
117
|
</slot>
|
|
@@ -190,5 +193,5 @@ const handleForgotPassword = async () => {
|
|
|
190
193
|
</template>
|
|
191
194
|
|
|
192
195
|
<style scoped>
|
|
193
|
-
section{background:linear-gradient(135deg,var(--color-bg-primary) 0,var(--color-bg-secondary) 100%);border:1px solid var(--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);margin:0 auto;max-width:25rem;padding:1em}form{gap:1.5rem}.form-group,form{display:flex;flex-direction:column}label{display:block;font-weight:500;margin-bottom:.5em}input[type=email],input[type=password],label{color:var(--color-gray-700);font-size:.875rem}input[type=email],input[type=password]{background-color:var(--color-bg-secondary);border:1px solid var(--color-border-light);border-radius
|
|
196
|
+
section{background:linear-gradient(135deg,var(--color-bg-primary) 0,var(--color-bg-secondary) 100%);border:1px solid var(--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);margin:0 auto;max-width:25rem;padding:1em}form{gap:1.5rem}.form-group,form{display:flex;flex-direction:column}label{display:block;font-weight:500;margin-bottom:.5em}input[type=email],input[type=password],label{color:var(--color-gray-700);font-size:.875rem}input[type=email],input[type=password]{background-color:var(--color-bg-secondary);border:1px solid var(--color-border-light);border-radius:.5em;box-sizing:border-box;padding:.75em 1em;transition:all .2s ease-in-out;width:100%}input[type=email]:focus,input[type=password]:focus{background-color:var(--color-bg-primary);border-color:var(--color-primary);box-shadow:0 0 0 3px rgba(59,130,246,.1);outline:none}.login-header{margin-bottom:1em;text-align:center}.login-title{color:var(--color-gray-800);font-size:1.875rem;font-weight:700;line-height:1.2;margin:0 0 .5em}.login-subtitle{color:var(--color-gray-500);font-size:.875rem;line-height:1.5;margin:0}.remember-me{align-items:center;display:flex;gap:.5rem}input[type=checkbox]{margin:0;width:auto}.remember-me label{color:var(--color-gray-700);font-size:.875rem;margin:0}button[type=submit]{align-items:center;background-color:var(--color-primary);border:1px solid var(--color-primary);border-radius:.5em;color:var(--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%}button[type=submit]:hover:not(:disabled){background-color:var(--color-primary-dark);border-color:var(--color-primary-dark)}button[type=submit]:disabled{cursor:not-allowed;opacity:.6}.loading-spinner{animation:spin 1s linear infinite;border:2px solid var(--color-white);border-radius:50%;border-top:2px solid transparent;height:16px;width:16px}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.login-footer{margin-top:1em;text-align:center}.forgot-password{margin:0}.forgot-link{align-items:center;color:var(--color-primary);display:inline-flex;font-size:.875rem;gap:.5rem;text-decoration:none;transition:color .2s ease-in-out}.forgot-link:hover:not(.loading){color:var(--color-primary-dark);text-decoration:underline}.forgot-link.loading{cursor:not-allowed;opacity:.6;pointer-events:none}.error-message{background-color:var(--color-error-light);border:1px solid var(--color-error-border);border-radius:.5em;color:var(--color-error)}.error-message,.success-message{font-size:.875rem;margin-top:1em;padding:.75em 1em;text-align:center}.success-message{background-color:var(--color-success-light);border:1px solid var(--color-success-border);border-radius:.5em;color:var(--color-success)}@media (max-width:480px){.login-form-container{margin:1em;padding:1.5em}.login-title{font-size:1.5rem}}
|
|
194
197
|
</style>
|
|
@@ -22,16 +22,16 @@ type __VLS_Slots = {} & {
|
|
|
22
22
|
'error-message'?: (props: typeof __VLS_15) => any;
|
|
23
23
|
};
|
|
24
24
|
declare const __VLS_component: import("vue").DefineComponent<LoginFormProps, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
25
|
-
error: (error: string) => any;
|
|
26
|
-
submit: (data: LoginFormData) => any;
|
|
27
25
|
success: (user: UserWithoutPassword) => any;
|
|
26
|
+
error: (error: string) => any;
|
|
28
27
|
"forgot-password-error": (error: string) => any;
|
|
28
|
+
submit: (data: LoginFormData) => any;
|
|
29
29
|
"forgot-password-success": () => any;
|
|
30
30
|
}, string, import("vue").PublicProps, Readonly<LoginFormProps> & Readonly<{
|
|
31
|
-
onError?: ((error: string) => any) | undefined;
|
|
32
|
-
onSubmit?: ((data: LoginFormData) => any) | undefined;
|
|
33
31
|
onSuccess?: ((user: UserWithoutPassword) => any) | undefined;
|
|
32
|
+
onError?: ((error: string) => any) | undefined;
|
|
34
33
|
"onForgot-password-error"?: ((error: string) => any) | undefined;
|
|
34
|
+
onSubmit?: ((data: LoginFormData) => any) | undefined;
|
|
35
35
|
"onForgot-password-success"?: (() => any) | undefined;
|
|
36
36
|
}>, {
|
|
37
37
|
apiEndpoint: string;
|
|
@@ -6,7 +6,14 @@ const props = defineProps({
|
|
|
6
6
|
redirectTo: { type: String, required: false, default: "/login" },
|
|
7
7
|
linkText: { type: String, required: false, default: "Logout" },
|
|
8
8
|
confirmMessage: { type: String, required: false, default: "Are you sure you want to logout?" },
|
|
9
|
-
class: { type: String, required: false }
|
|
9
|
+
class: { type: String, required: false },
|
|
10
|
+
style: { type: [String, Object], required: false },
|
|
11
|
+
linkClass: { type: String, required: false },
|
|
12
|
+
linkStyle: { type: [String, Object], required: false },
|
|
13
|
+
errorClass: { type: String, required: false },
|
|
14
|
+
errorStyle: { type: [String, Object], required: false },
|
|
15
|
+
containerClass: { type: String, required: false },
|
|
16
|
+
containerStyle: { type: [String, Object], required: false }
|
|
10
17
|
});
|
|
11
18
|
const emit = defineEmits(["success", "click", "error"]);
|
|
12
19
|
const { logout } = useAuth();
|
|
@@ -37,26 +44,33 @@ const handleLogout = async (event) => {
|
|
|
37
44
|
</script>
|
|
38
45
|
|
|
39
46
|
<template>
|
|
40
|
-
<div
|
|
47
|
+
<div
|
|
48
|
+
:class="['logout-container', props.containerClass]"
|
|
49
|
+
:style="props.containerStyle"
|
|
50
|
+
>
|
|
41
51
|
<div
|
|
42
52
|
v-if="error"
|
|
43
|
-
class="error-message"
|
|
53
|
+
:class="['error-message', props.errorClass]"
|
|
54
|
+
:style="props.errorStyle"
|
|
44
55
|
>
|
|
45
56
|
{{ error }}
|
|
46
57
|
</div>
|
|
47
58
|
|
|
48
59
|
<a
|
|
49
60
|
href="#"
|
|
50
|
-
:class="['logout-link', { loading: isLoading }, props.
|
|
61
|
+
:class="['logout-link', { loading: isLoading }, props.linkClass]"
|
|
62
|
+
:style="props.linkStyle"
|
|
51
63
|
:disabled="isLoading"
|
|
52
64
|
@click="handleLogout"
|
|
53
65
|
>
|
|
54
|
-
<
|
|
55
|
-
|
|
66
|
+
<slot>
|
|
67
|
+
<span v-if="isLoading">Logging out...</span>
|
|
68
|
+
<span v-else>{{ linkText }}</span>
|
|
69
|
+
</slot>
|
|
56
70
|
</a>
|
|
57
71
|
</div>
|
|
58
72
|
</template>
|
|
59
73
|
|
|
60
74
|
<style scoped>
|
|
61
|
-
.logout-link{color
|
|
75
|
+
.logout-link{background:var(--color-error);border:2px solid transparent;border-color:var(--color-error-dark);border-radius:.5em;color:#fff;display:inline-block;font-weight:500;padding:.5em 1em;text-decoration:none;transition:all .2s}.logout-link:hover{border-color:var(--color-primary)}.error-message{color:var(--color-error);font-size:1rem;margin-bottom:.5em}
|
|
62
76
|
</style>
|
|
@@ -3,18 +3,35 @@ interface LogoutLinkProps {
|
|
|
3
3
|
linkText?: string;
|
|
4
4
|
confirmMessage?: string;
|
|
5
5
|
class?: string;
|
|
6
|
+
style?: string | Record<string, string | number>;
|
|
7
|
+
linkClass?: string;
|
|
8
|
+
linkStyle?: string | Record<string, string | number>;
|
|
9
|
+
errorClass?: string;
|
|
10
|
+
errorStyle?: string | Record<string, string | number>;
|
|
11
|
+
containerClass?: string;
|
|
12
|
+
containerStyle?: string | Record<string, string | number>;
|
|
6
13
|
}
|
|
7
|
-
declare
|
|
8
|
-
|
|
9
|
-
|
|
14
|
+
declare var __VLS_1: {};
|
|
15
|
+
type __VLS_Slots = {} & {
|
|
16
|
+
default?: (props: typeof __VLS_1) => any;
|
|
17
|
+
};
|
|
18
|
+
declare const __VLS_component: import("vue").DefineComponent<LogoutLinkProps, void, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
10
19
|
success: () => any;
|
|
20
|
+
error: (error: string) => any;
|
|
21
|
+
click: () => any;
|
|
11
22
|
}, string, import("vue").PublicProps, Readonly<LogoutLinkProps> & Readonly<{
|
|
12
|
-
onClick?: (() => any) | undefined;
|
|
13
|
-
onError?: ((error: string) => any) | undefined;
|
|
14
23
|
onSuccess?: (() => any) | undefined;
|
|
24
|
+
onError?: ((error: string) => any) | undefined;
|
|
25
|
+
onClick?: (() => any) | undefined;
|
|
15
26
|
}>, {
|
|
16
27
|
redirectTo: string;
|
|
17
28
|
linkText: string;
|
|
18
29
|
confirmMessage: string;
|
|
19
30
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
31
|
+
declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
|
|
20
32
|
export default _default;
|
|
33
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
34
|
+
new (): {
|
|
35
|
+
$slots: S;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
@@ -1,49 +1,60 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed } from "vue";
|
|
3
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
4
|
const props = defineProps({
|
|
4
5
|
password: { type: String, required: true },
|
|
5
6
|
validationResult: { type: [Object, null], required: true },
|
|
6
7
|
showHints: { type: Boolean, required: false, default: true },
|
|
7
8
|
showRules: { type: Boolean, required: false, default: true }
|
|
8
9
|
});
|
|
10
|
+
const { public: { nuxtUsers } } = useRuntimeConfig();
|
|
11
|
+
const moduleOptions = nuxtUsers;
|
|
9
12
|
const hasErrors = computed(() => (props.validationResult?.errors?.length || 0) > 0);
|
|
10
13
|
const hasHints = computed(() => (props.validationResult?.hints?.length || 0) > 0);
|
|
11
14
|
const hasPassword = computed(() => props.password.length > 0);
|
|
12
15
|
const validationRules = computed(() => {
|
|
13
16
|
if (!props.password) return [];
|
|
14
17
|
const rules = [];
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
18
|
+
if (moduleOptions.passwordValidation.minLength) {
|
|
19
|
+
const hasMinLength = props.password.length >= moduleOptions.passwordValidation.minLength;
|
|
20
|
+
rules.push({
|
|
21
|
+
text: `At least ${moduleOptions.passwordValidation.minLength} characters`,
|
|
22
|
+
passed: hasMinLength
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (moduleOptions.passwordValidation.requireUppercase) {
|
|
26
|
+
const hasUppercase = /[A-Z]/.test(props.password);
|
|
27
|
+
rules.push({
|
|
28
|
+
text: "Contains uppercase letter (A-Z)",
|
|
29
|
+
passed: hasUppercase
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if (moduleOptions.passwordValidation.requireLowercase) {
|
|
33
|
+
const hasLowercase = /[a-z]/.test(props.password);
|
|
34
|
+
rules.push({
|
|
35
|
+
text: "Contains lowercase letter (a-z)",
|
|
36
|
+
passed: hasLowercase
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
if (moduleOptions.passwordValidation.requireNumbers) {
|
|
40
|
+
const hasNumbers = /\d/.test(props.password);
|
|
41
|
+
rules.push({
|
|
42
|
+
text: "Contains number (0-9)",
|
|
43
|
+
passed: hasNumbers
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (moduleOptions.passwordValidation.requireSpecialChars) {
|
|
47
|
+
const hasSpecialChars = /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(props.password);
|
|
48
|
+
rules.push({
|
|
49
|
+
text: "Contains special character (!@#$%^&*)",
|
|
50
|
+
passed: hasSpecialChars
|
|
51
|
+
});
|
|
52
|
+
}
|
|
41
53
|
return rules;
|
|
42
54
|
});
|
|
43
55
|
</script>
|
|
44
56
|
|
|
45
57
|
<template>
|
|
46
|
-
<!-- Password strength indicator -->
|
|
47
58
|
<div
|
|
48
59
|
v-if="hasPassword"
|
|
49
60
|
class="password-strength"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useAuth } from "../composables/useAuth";
|
|
3
|
+
const { user } = useAuth();
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div>
|
|
8
|
+
<h2>Profile Information</h2>
|
|
9
|
+
<dl
|
|
10
|
+
v-if="user"
|
|
11
|
+
class="profile-info"
|
|
12
|
+
>
|
|
13
|
+
<dt>Name:</dt>
|
|
14
|
+
<dd>{{ user.name }}</dd>
|
|
15
|
+
|
|
16
|
+
<dt>Email:</dt>
|
|
17
|
+
<dd>{{ user.email }}</dd>
|
|
18
|
+
|
|
19
|
+
<dt>Role:</dt>
|
|
20
|
+
<dd>{{ user.role }}</dd>
|
|
21
|
+
|
|
22
|
+
<dt>Registered:</dt>
|
|
23
|
+
<dd>{{ Intl.DateTimeFormat().format(new Date(user.created_at)) }}</dd>
|
|
24
|
+
|
|
25
|
+
<dt>Last updated:</dt>
|
|
26
|
+
<dd>{{ Intl.DateTimeFormat().format(new Date(user.updated_at)) }}</dd>
|
|
27
|
+
</dl>
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<style scoped>
|
|
32
|
+
div{background-color:var(--color-bg-primary);border-top-left-radius:.5em;border-top-right-radius:.5em;margin:0 auto;max-width:42em;padding:1em}h2{border-bottom:.1em solid var(--color-border)}dl{display:grid;font-size:1rem;gap:16px;grid-template-columns:1fr 5fr;list-style:none;margin:0;padding:0}
|
|
33
|
+
</style>
|