@frigear-nu/account 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -6
- package/dist/module.d.mts +31 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +41 -36
- package/dist/runtime/app/components/Account/AccountHeader.d.vue.ts +3 -0
- package/dist/runtime/app/components/Account/AccountHeader.vue +59 -0
- package/dist/runtime/app/components/Account/AccountHeader.vue.d.ts +3 -0
- package/dist/runtime/app/components/Account/AccountMenu.vue +7 -2
- package/dist/runtime/app/components/Account/IdentificationCode.d.vue.ts +3 -0
- package/dist/runtime/app/components/Account/IdentificationCode.vue +82 -0
- package/dist/runtime/app/components/Account/IdentificationCode.vue.d.ts +3 -0
- package/dist/runtime/app/components/Account/IdentificationCodeDialog.d.vue.ts +20 -0
- package/dist/runtime/app/components/Account/IdentificationCodeDialog.vue +16 -0
- package/dist/runtime/app/components/Account/IdentificationCodeDialog.vue.d.ts +20 -0
- package/dist/runtime/app/composables/useAccount.js +3 -3
- package/dist/runtime/app/pages/api-tokens.d.vue.ts +3 -0
- package/dist/runtime/app/pages/api-tokens.vue +308 -0
- package/dist/runtime/app/pages/api-tokens.vue.d.ts +3 -0
- package/dist/runtime/app/pages/documents.d.vue.ts +3 -0
- package/dist/runtime/app/pages/documents.vue +38 -0
- package/dist/runtime/app/pages/documents.vue.d.ts +3 -0
- package/dist/runtime/app/pages/id-code.d.vue.ts +3 -0
- package/dist/runtime/app/pages/id-code.vue +13 -0
- package/dist/runtime/app/pages/id-code.vue.d.ts +3 -0
- package/dist/runtime/app/pages/layout.d.vue.ts +3 -0
- package/dist/runtime/app/pages/layout.vue +10 -0
- package/dist/runtime/app/pages/layout.vue.d.ts +3 -0
- package/dist/runtime/app/pages/profile.vue +188 -46
- package/dist/runtime/app/pages/security.vue +180 -60
- package/dist/runtime/app/utils/documents.d.ts +5 -0
- package/dist/runtime/app/utils/documents.js +7 -0
- package/dist/runtime/i18n/lang/da.json +138 -14
- package/dist/runtime/i18n/lang/en.json +138 -14
- package/dist/runtime/menus.js +42 -0
- package/dist/runtime/server/routes/account/id-code.get.js +20 -0
- package/package.json +25 -11
- package/dist/runtime/server/db/schema.sqlite.d.ts +0 -24
- package/dist/runtime/server/db/schema.sqlite.js +0 -4
- package/dist/runtime/server/routes/account/change-email.post.js +0 -20
- package/dist/runtime/server/routes/account/change-password.post.d.ts +0 -2
- package/dist/runtime/server/routes/account/change-password.post.js +0 -26
- package/dist/runtime/server/routes/account/profile.post.d.ts +0 -2
- package/dist/runtime/server/routes/account/profile.post.js +0 -17
- /package/dist/runtime/server/routes/account/{change-email.post.d.ts → id-code.get.d.ts} +0 -0
|
@@ -2,38 +2,76 @@
|
|
|
2
2
|
"account": {
|
|
3
3
|
"title": "Account",
|
|
4
4
|
"menu": {
|
|
5
|
-
"label": "Dashboard Menu"
|
|
5
|
+
"label": "Dashboard Menu",
|
|
6
|
+
"account": {
|
|
7
|
+
"label": "Account",
|
|
8
|
+
"description": "Your account."
|
|
9
|
+
},
|
|
10
|
+
"profile": {
|
|
11
|
+
"label": "Profile",
|
|
12
|
+
"description": "Manage your profile details and email address."
|
|
13
|
+
},
|
|
14
|
+
"security": {
|
|
15
|
+
"label": "Security",
|
|
16
|
+
"description": "Change your password and manage passkeys."
|
|
17
|
+
},
|
|
18
|
+
"documents": {
|
|
19
|
+
"label": "Documents",
|
|
20
|
+
"description": "Handbooks and other files for you."
|
|
21
|
+
},
|
|
22
|
+
"apiTokens": {
|
|
23
|
+
"label": "API tokens",
|
|
24
|
+
"description": "Create tokens for scripts and tools."
|
|
25
|
+
}
|
|
6
26
|
},
|
|
7
27
|
"identificationCode": {
|
|
8
28
|
"title": "Frigear ID",
|
|
9
|
-
"description": "Press here to show your ID."
|
|
29
|
+
"description": "Press here to show your ID.",
|
|
30
|
+
"expiresIn": "Refreshes in {time}s",
|
|
31
|
+
"refresh": "Refresh",
|
|
32
|
+
"role": "Role",
|
|
33
|
+
"error": "Could not create your Frigear ID."
|
|
10
34
|
},
|
|
11
35
|
"profile": {
|
|
12
36
|
"title": "Profile",
|
|
13
37
|
"description": "Change your information.",
|
|
14
38
|
"name": {
|
|
15
39
|
"label": "Name",
|
|
16
|
-
"description": "Your full name."
|
|
40
|
+
"description": "Your full name.",
|
|
41
|
+
"required": "Please enter your name."
|
|
17
42
|
},
|
|
18
43
|
"email": {
|
|
19
44
|
"label": "E-mail",
|
|
20
45
|
"description": "Your awesome E-mail.",
|
|
21
46
|
"change": {
|
|
22
47
|
"title": "Change e-mail",
|
|
23
|
-
"label": "New e-mail"
|
|
24
|
-
|
|
48
|
+
"label": "New e-mail",
|
|
49
|
+
"sent": {
|
|
50
|
+
"title": "Check your inbox",
|
|
51
|
+
"description": "We sent a link to your new address. It takes effect once you confirm it."
|
|
52
|
+
},
|
|
53
|
+
"done": "Your email address was changed.",
|
|
54
|
+
"failed": "This confirmation link is invalid or has expired.",
|
|
55
|
+
"error": "Could not change your email address."
|
|
56
|
+
},
|
|
57
|
+
"invalid": "Enter a valid email address."
|
|
25
58
|
},
|
|
26
59
|
"avatar": {
|
|
27
60
|
"label": "Avatar",
|
|
28
|
-
"description": "
|
|
29
|
-
"url": "Avatar URL"
|
|
61
|
+
"description": "Link to a square image.",
|
|
62
|
+
"url": "Avatar URL",
|
|
63
|
+
"invalid": "Enter a valid URL."
|
|
30
64
|
},
|
|
31
|
-
"save": "Save profile"
|
|
65
|
+
"save": "Save profile",
|
|
66
|
+
"saved": "Profile updated",
|
|
67
|
+
"saveFailed": "Could not update your profile.",
|
|
68
|
+
"cancel": "Cancel"
|
|
32
69
|
},
|
|
33
70
|
"documents": {
|
|
34
71
|
"title": "Documents",
|
|
35
72
|
"description": "Your documents.",
|
|
36
|
-
"download": "Download"
|
|
73
|
+
"download": "Download",
|
|
74
|
+
"empty": "No documents for you yet."
|
|
37
75
|
},
|
|
38
76
|
"security": {
|
|
39
77
|
"title": "Security",
|
|
@@ -46,19 +84,105 @@
|
|
|
46
84
|
"title": "Change password",
|
|
47
85
|
"submit": "Change password",
|
|
48
86
|
"new": {
|
|
49
|
-
"label": "New password"
|
|
87
|
+
"label": "New password",
|
|
88
|
+
"min": "Use at least 8 characters."
|
|
50
89
|
},
|
|
51
90
|
"confirm": {
|
|
52
|
-
"label": "Confirm new password"
|
|
91
|
+
"label": "Confirm new password",
|
|
92
|
+
"mismatch": "The passwords do not match."
|
|
53
93
|
},
|
|
54
94
|
"current": {
|
|
55
|
-
"label": "Current password"
|
|
56
|
-
|
|
95
|
+
"label": "Current password",
|
|
96
|
+
"required": "Enter your current password."
|
|
97
|
+
},
|
|
98
|
+
"success": "Your password was changed.",
|
|
99
|
+
"error": "Could not change your password."
|
|
57
100
|
},
|
|
58
101
|
"passkeys": {
|
|
59
102
|
"title": "Passkeys",
|
|
60
|
-
"register": "Register a passkey"
|
|
103
|
+
"register": "Register a passkey",
|
|
104
|
+
"description": "Sign in with your fingerprint, face or device PIN instead of a password.",
|
|
105
|
+
"unnamed": "Passkey",
|
|
106
|
+
"created": "Added {date}",
|
|
107
|
+
"remove": "Remove passkey",
|
|
108
|
+
"empty": "You have not added any passkeys yet.",
|
|
109
|
+
"name": {
|
|
110
|
+
"label": "Passkey name",
|
|
111
|
+
"placeholder": "Name, e.g. MacBook"
|
|
112
|
+
},
|
|
113
|
+
"registered": "Passkey added.",
|
|
114
|
+
"registerError": "Could not register the passkey.",
|
|
115
|
+
"removed": "Passkey removed.",
|
|
116
|
+
"removeError": "Could not remove the passkey.",
|
|
117
|
+
"loadError": "Could not load your passkeys."
|
|
61
118
|
}
|
|
119
|
+
},
|
|
120
|
+
"backTo": "Back to account",
|
|
121
|
+
"signedInAs": "Signed in as",
|
|
122
|
+
"apiTokens": {
|
|
123
|
+
"title": "API tokens",
|
|
124
|
+
"description": "Create tokens for scripts and tools.",
|
|
125
|
+
"allScopes": "Full access",
|
|
126
|
+
"copy": "Copy",
|
|
127
|
+
"copied": "Token copied.",
|
|
128
|
+
"copiedSnippet": "Copied.",
|
|
129
|
+
"copyError": "Could not copy. Select the token and copy it manually.",
|
|
130
|
+
"create": {
|
|
131
|
+
"title": "Create an API token",
|
|
132
|
+
"description": "Tokens let scripts and tools such as MCP clients act as you. Give each one only the scopes it needs.",
|
|
133
|
+
"submit": "Create token"
|
|
134
|
+
},
|
|
135
|
+
"createError": "Could not create the token.",
|
|
136
|
+
"name": {
|
|
137
|
+
"label": "Name",
|
|
138
|
+
"placeholder": "e.g. My MCP client",
|
|
139
|
+
"required": "Give the token a name."
|
|
140
|
+
},
|
|
141
|
+
"scopes": {
|
|
142
|
+
"label": "Scopes",
|
|
143
|
+
"placeholder": "Choose what the token may do",
|
|
144
|
+
"required": "Pick at least one scope."
|
|
145
|
+
},
|
|
146
|
+
"expiry": {
|
|
147
|
+
"label": "Expires",
|
|
148
|
+
"30": "30 days",
|
|
149
|
+
"90": "90 days",
|
|
150
|
+
"365": "1 year",
|
|
151
|
+
"never": "No expiry"
|
|
152
|
+
},
|
|
153
|
+
"reveal": {
|
|
154
|
+
"title": "Copy your new token now",
|
|
155
|
+
"description": "This is the only time it is shown. If you lose it, create a new one.",
|
|
156
|
+
"label": "New token",
|
|
157
|
+
"done": "Done"
|
|
158
|
+
},
|
|
159
|
+
"connect": {
|
|
160
|
+
"title": "Connect to MCP",
|
|
161
|
+
"description": "Use your account from an AI assistant. Your API token is only filled into the commands right after you create it.",
|
|
162
|
+
"claude": {
|
|
163
|
+
"steps": "Claude.ai and Claude Desktop: open Settings → Connectors → Add custom connector, paste the URL below and sign in when asked. No token needed.",
|
|
164
|
+
"cli": "Claude Code (uses an API token):"
|
|
165
|
+
},
|
|
166
|
+
"chatgpt": {
|
|
167
|
+
"steps": "ChatGPT: open Settings → Connectors → Advanced, turn on Developer mode, then create a connector with the URL below. Choose OAuth and sign in when asked. No token needed."
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"list": {
|
|
171
|
+
"title": "Your tokens",
|
|
172
|
+
"description": "Revoke a token to stop it working immediately.",
|
|
173
|
+
"empty": "You have not created any tokens yet.",
|
|
174
|
+
"created": "Created {date}",
|
|
175
|
+
"lastUsed": "Last used {date}",
|
|
176
|
+
"expires": "Expires {date}"
|
|
177
|
+
},
|
|
178
|
+
"status": {
|
|
179
|
+
"ok": "Active",
|
|
180
|
+
"revoked": "Revoked",
|
|
181
|
+
"expired": "Expired"
|
|
182
|
+
},
|
|
183
|
+
"revoke": "Revoke token",
|
|
184
|
+
"revoked": "Token revoked.",
|
|
185
|
+
"revokeError": "Could not revoke the token."
|
|
62
186
|
}
|
|
63
187
|
}
|
|
64
188
|
}
|
package/dist/runtime/menus.js
CHANGED
|
@@ -5,7 +5,9 @@ export default defineMenuSource(() => {
|
|
|
5
5
|
extendMenuItem("account.dropdown", {
|
|
6
6
|
id: "account",
|
|
7
7
|
label: "Account",
|
|
8
|
+
labelKey: "account.menu.account.label",
|
|
8
9
|
description: "Your account.",
|
|
10
|
+
descriptionKey: "account.menu.account.description",
|
|
9
11
|
icon: "i-lucide-layout-dashboard",
|
|
10
12
|
to: config.pages.index,
|
|
11
13
|
order: -99
|
|
@@ -15,7 +17,9 @@ export default defineMenuSource(() => {
|
|
|
15
17
|
const profileItem = defineMenuItem({
|
|
16
18
|
id: "profile",
|
|
17
19
|
label: "Profile",
|
|
20
|
+
labelKey: "account.menu.profile.label",
|
|
18
21
|
description: "Manage your profile details and email address.",
|
|
22
|
+
descriptionKey: "account.menu.profile.description",
|
|
19
23
|
icon: "i-lucide-user-round-pen",
|
|
20
24
|
to: config.pages.profile,
|
|
21
25
|
order: 10
|
|
@@ -27,10 +31,48 @@ export default defineMenuSource(() => {
|
|
|
27
31
|
extendMenuItem("account", {
|
|
28
32
|
id: "security",
|
|
29
33
|
label: "Security",
|
|
34
|
+
labelKey: "account.menu.security.label",
|
|
30
35
|
description: "Change your password and manage passkeys.",
|
|
36
|
+
descriptionKey: "account.menu.security.description",
|
|
31
37
|
icon: "i-lucide-shield-user",
|
|
32
38
|
to: config.pages.security,
|
|
33
39
|
order: 20
|
|
34
40
|
});
|
|
35
41
|
}
|
|
42
|
+
if (config?.pages?.apiTokens) {
|
|
43
|
+
extendMenuItem("account", {
|
|
44
|
+
id: "api-tokens",
|
|
45
|
+
label: "API tokens",
|
|
46
|
+
labelKey: "account.menu.apiTokens.label",
|
|
47
|
+
description: "Create tokens for scripts and tools.",
|
|
48
|
+
descriptionKey: "account.menu.apiTokens.description",
|
|
49
|
+
icon: "i-lucide-key-round",
|
|
50
|
+
to: config.pages.apiTokens,
|
|
51
|
+
order: 25
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (config?.pages?.documents) {
|
|
55
|
+
extendMenuItem("account", {
|
|
56
|
+
id: "documents",
|
|
57
|
+
label: "Documents",
|
|
58
|
+
labelKey: "account.menu.documents.label",
|
|
59
|
+
description: "Handbooks and other files for you.",
|
|
60
|
+
descriptionKey: "account.menu.documents.description",
|
|
61
|
+
icon: "i-lucide-files",
|
|
62
|
+
to: config.pages.documents,
|
|
63
|
+
order: 30
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (config?.pages?.idCode) {
|
|
67
|
+
extendMenuItem("account", {
|
|
68
|
+
id: "id-code",
|
|
69
|
+
label: "Frigear ID",
|
|
70
|
+
labelKey: "account.identificationCode.title",
|
|
71
|
+
description: "Press here to show your ID.",
|
|
72
|
+
descriptionKey: "account.identificationCode.description",
|
|
73
|
+
icon: "i-lucide-qr-code",
|
|
74
|
+
to: config.pages.idCode,
|
|
75
|
+
order: 5
|
|
76
|
+
});
|
|
77
|
+
}
|
|
36
78
|
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SignJWT, importPKCS8 } from "jose";
|
|
2
|
+
const TTL_SECONDS = 60;
|
|
3
|
+
function normalizePem(raw) {
|
|
4
|
+
return raw.trim().replace(/^["']|["']$/g, "").replace(/\\n/g, "\n").trim();
|
|
5
|
+
}
|
|
6
|
+
export default defineEventHandler(async (event) => {
|
|
7
|
+
const user = await requireUser(event, { sessionOnly: true });
|
|
8
|
+
const config = useRuntimeConfig(event);
|
|
9
|
+
const own = config.account?.idCode;
|
|
10
|
+
const oauth = config.oauthServer;
|
|
11
|
+
const pem = normalizePem(own?.privateKey || oauth?.privateKey || "");
|
|
12
|
+
if (!pem) {
|
|
13
|
+
throw createError({ statusCode: 501, statusMessage: "Frigear ID is not configured: set a signing key." });
|
|
14
|
+
}
|
|
15
|
+
const keyId = own?.keyId || oauth?.keyId || "key-1";
|
|
16
|
+
const issuer = own?.issuer || oauth?.issuer || getRequestURL(event).origin;
|
|
17
|
+
const key = await importPKCS8(pem, "RS256");
|
|
18
|
+
const jwt = await new SignJWT({ typ: "frigear:id" }).setProtectedHeader({ alg: "RS256", kid: keyId }).setIssuer(issuer).setSubject(String(user.id)).setAudience("frigear:id").setIssuedAt().setExpirationTime(`${TTL_SECONDS}s`).setJti(crypto.randomUUID()).sign(key);
|
|
19
|
+
return { idCode: jwt, payload: `urn:frigear:id:${jwt}`, expiresIn: TTL_SECONDS };
|
|
20
|
+
});
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frigear-nu/account",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "A module for managing the account area (profile, email, password, security) in the main frigear app.",
|
|
5
|
+
"homepage": "https://docs.frigear.dev/modules/account",
|
|
5
6
|
"repository": {
|
|
6
7
|
"type": "git",
|
|
7
|
-
"url": "
|
|
8
|
+
"url": "git+https://github.com/frigear-nu/frigear-nu.git",
|
|
9
|
+
"directory": "packages/account"
|
|
8
10
|
},
|
|
9
11
|
"license": "MIT",
|
|
10
12
|
"type": "module",
|
|
@@ -22,31 +24,44 @@
|
|
|
22
24
|
"playground"
|
|
23
25
|
],
|
|
24
26
|
"dependencies": {
|
|
25
|
-
"@frigear-nu/
|
|
27
|
+
"@frigear-nu/module-kit": "0.1.7",
|
|
28
|
+
"@frigear-nu/auth": "0.0.3",
|
|
29
|
+
"@frigear-nu/users": "1.0.9",
|
|
26
30
|
"@libsql/client": "^0.18.0",
|
|
27
31
|
"@nuxt/kit": "^4.6.0",
|
|
32
|
+
"@vueuse/core": "^15.0.0",
|
|
28
33
|
"drizzle-kit": "^0.31.11",
|
|
29
34
|
"drizzle-orm": "^0.45.3",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"@vueuse/core": "^15.0.0"
|
|
35
|
+
"jose": "^6.1.0",
|
|
36
|
+
"uqr": "^0.1.2",
|
|
37
|
+
"zod": "^4.6.5"
|
|
34
38
|
},
|
|
35
39
|
"peerDependencies": {
|
|
36
|
-
"@
|
|
37
|
-
"@
|
|
40
|
+
"@frigear-nu/authorization": "0.0.6",
|
|
41
|
+
"@frigear-nu/menu": "0.0.9",
|
|
42
|
+
"@nuxt/ui": "^4.10.0",
|
|
43
|
+
"@nuxthub/core": "^0.10.8"
|
|
38
44
|
},
|
|
39
45
|
"peerDependenciesMeta": {
|
|
46
|
+
"@frigear-nu/authorization": {
|
|
47
|
+
"optional": true
|
|
48
|
+
},
|
|
49
|
+
"@frigear-nu/menu": {
|
|
50
|
+
"optional": true
|
|
51
|
+
},
|
|
40
52
|
"@nuxt/ui": {
|
|
41
53
|
"optional": true
|
|
42
54
|
}
|
|
43
55
|
},
|
|
44
56
|
"devDependencies": {
|
|
57
|
+
"@frigear-nu/authorization": "0.0.6",
|
|
58
|
+
"@frigear-nu/menu": "0.0.9",
|
|
45
59
|
"@nuxt/devtools": "^3.4.2",
|
|
46
60
|
"@nuxt/eslint-config": "^1.17.0",
|
|
47
61
|
"@nuxt/module-builder": "^1.0.3",
|
|
48
62
|
"@nuxt/schema": "^4.6.0",
|
|
49
63
|
"@nuxt/test-utils": "^4.3.2",
|
|
64
|
+
"@nuxt/ui": "^4.11.0",
|
|
50
65
|
"@nuxthub/core": "^0.10.8",
|
|
51
66
|
"@types/node": "latest",
|
|
52
67
|
"changelogen": "^0.6.2",
|
|
@@ -54,8 +69,7 @@
|
|
|
54
69
|
"nuxt": "^4.6.1",
|
|
55
70
|
"typescript": "^6.0.3",
|
|
56
71
|
"vitest": "^5.0.1",
|
|
57
|
-
"vue-tsc": "^3.3.11"
|
|
58
|
-
"@nuxt/ui": "^4.11.0"
|
|
72
|
+
"vue-tsc": "^3.3.11"
|
|
59
73
|
},
|
|
60
74
|
"scripts": {
|
|
61
75
|
"postinstall": "nuxt-module-build build --stub && nuxt prepare",
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export declare const users: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
2
|
-
name: "users";
|
|
3
|
-
schema: undefined;
|
|
4
|
-
columns: {
|
|
5
|
-
acceptedTermsAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
6
|
-
name: "accepted_terms_at";
|
|
7
|
-
tableName: "users";
|
|
8
|
-
dataType: "number";
|
|
9
|
-
columnType: "SQLiteInteger";
|
|
10
|
-
data: number;
|
|
11
|
-
driverParam: number;
|
|
12
|
-
notNull: false;
|
|
13
|
-
hasDefault: false;
|
|
14
|
-
isPrimaryKey: false;
|
|
15
|
-
isAutoincrement: false;
|
|
16
|
-
hasRuntimeDefault: false;
|
|
17
|
-
enumValues: undefined;
|
|
18
|
-
baseColumn: never;
|
|
19
|
-
identity: undefined;
|
|
20
|
-
generated: undefined;
|
|
21
|
-
}, {}, {}>;
|
|
22
|
-
};
|
|
23
|
-
dialect: "sqlite";
|
|
24
|
-
}>;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { db, schema } from "@nuxthub/db";
|
|
2
|
-
import { eq } from "drizzle-orm";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
const bodySchema = z.object({
|
|
5
|
-
email: z.string().email()
|
|
6
|
-
});
|
|
7
|
-
export default defineEventHandler(async (event) => {
|
|
8
|
-
const { user } = await requireUserSession(event);
|
|
9
|
-
const { email } = await readValidatedBody(event, bodySchema.parse);
|
|
10
|
-
const existing = await db.query.users.findFirst({ where: (t, { eq: eq2 }) => eq2(t.email, email) });
|
|
11
|
-
if (existing && existing.id !== user.id) {
|
|
12
|
-
throw createError({ statusCode: 409, statusMessage: "A user with this email already exists." });
|
|
13
|
-
}
|
|
14
|
-
const [updated] = await db.update(schema.users).set({ email }).where(eq(schema.users.id, user.id)).returning();
|
|
15
|
-
if (!updated) {
|
|
16
|
-
throw createError({ statusCode: 404, statusMessage: "User not found." });
|
|
17
|
-
}
|
|
18
|
-
await setUserSession(event, { user: mapUserToSession(updated) });
|
|
19
|
-
return { id: updated.id };
|
|
20
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { db, schema } from "@nuxthub/db";
|
|
2
|
-
import { eq } from "drizzle-orm";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
const bodySchema = z.object({
|
|
5
|
-
currentPassword: z.string().min(1),
|
|
6
|
-
newPassword: z.string().min(8),
|
|
7
|
-
confirmNewPassword: z.string().min(8)
|
|
8
|
-
}).refine((data) => data.newPassword === data.confirmNewPassword, {
|
|
9
|
-
message: "Passwords do not match.",
|
|
10
|
-
path: ["confirmNewPassword"]
|
|
11
|
-
});
|
|
12
|
-
export default defineEventHandler(async (event) => {
|
|
13
|
-
const { user } = await requireUserSession(event);
|
|
14
|
-
const { currentPassword, newPassword } = await readValidatedBody(event, bodySchema.parse);
|
|
15
|
-
const dbUser = await db.query.users.findFirst({ where: (t, { eq: eq2 }) => eq2(t.id, user.id) });
|
|
16
|
-
if (!dbUser?.password || !await verifyPassword(dbUser.password, currentPassword)) {
|
|
17
|
-
throw createError({ statusCode: 401, statusMessage: "Your current password is incorrect." });
|
|
18
|
-
}
|
|
19
|
-
const hashed = await hashPassword(newPassword);
|
|
20
|
-
const [updated] = await db.update(schema.users).set({ password: hashed }).where(eq(schema.users.id, user.id)).returning();
|
|
21
|
-
if (!updated) {
|
|
22
|
-
throw createError({ statusCode: 404, statusMessage: "User not found." });
|
|
23
|
-
}
|
|
24
|
-
await setUserSession(event, { user: mapUserToSession(updated) });
|
|
25
|
-
return { id: updated.id };
|
|
26
|
-
});
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { db, schema } from "@nuxthub/db";
|
|
2
|
-
import { eq } from "drizzle-orm";
|
|
3
|
-
import { z } from "zod";
|
|
4
|
-
const bodySchema = z.object({
|
|
5
|
-
name: z.string().min(1),
|
|
6
|
-
avatar: z.string().optional()
|
|
7
|
-
});
|
|
8
|
-
export default defineEventHandler(async (event) => {
|
|
9
|
-
const { user } = await requireUserSession(event);
|
|
10
|
-
const { name, avatar } = await readValidatedBody(event, bodySchema.parse);
|
|
11
|
-
const [updated] = await db.update(schema.users).set({ name, avatar }).where(eq(schema.users.id, user.id)).returning();
|
|
12
|
-
if (!updated) {
|
|
13
|
-
throw createError({ statusCode: 404, statusMessage: "User not found." });
|
|
14
|
-
}
|
|
15
|
-
await setUserSession(event, { user: mapUserToSession(updated) });
|
|
16
|
-
return { id: updated.id };
|
|
17
|
-
});
|
|
File without changes
|