Feat: Hash metrics password (#1778)
hash the metrics password if it is not already hashed
This commit is contained in:
@@ -135,7 +135,7 @@
|
|||||||
"sessionTimeoutDesc": "Session duration for Remember Me (seconds)",
|
"sessionTimeoutDesc": "Session duration for Remember Me (seconds)",
|
||||||
"metrics": "Metrics",
|
"metrics": "Metrics",
|
||||||
"metricsPassword": "Password",
|
"metricsPassword": "Password",
|
||||||
"metricsPasswordDesc": "Bearer Password for the metrics endpoint (argon2 hash)",
|
"metricsPasswordDesc": "Bearer Password for the metrics endpoint (password or argon2 hash)",
|
||||||
"json": "JSON",
|
"json": "JSON",
|
||||||
"jsonDesc": "Route for metrics in JSON format",
|
"jsonDesc": "Route for metrics in JSON format",
|
||||||
"prometheus": "Prometheus",
|
"prometheus": "Prometheus",
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
"@libsql/client": "^0.15.1",
|
"@libsql/client": "^0.15.1",
|
||||||
"@nuxtjs/i18n": "^9.4.0",
|
"@nuxtjs/i18n": "^9.4.0",
|
||||||
"@nuxtjs/tailwindcss": "^6.13.2",
|
"@nuxtjs/tailwindcss": "^6.13.2",
|
||||||
|
"@phc/format": "^1.0.0",
|
||||||
"@pinia/nuxt": "^0.10.1",
|
"@pinia/nuxt": "^0.10.1",
|
||||||
"@tailwindcss/forms": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
"apexcharts": "^4.5.0",
|
"apexcharts": "^4.5.0",
|
||||||
@@ -51,6 +52,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxt/eslint": "1.3.0",
|
"@nuxt/eslint": "1.3.0",
|
||||||
"@types/debug": "^4.1.12",
|
"@types/debug": "^4.1.12",
|
||||||
|
"@types/phc__format": "^1.0.1",
|
||||||
"@types/qrcode": "^1.5.5",
|
"@types/qrcode": "^1.5.5",
|
||||||
"@types/semver": "^7.7.0",
|
"@types/semver": "^7.7.0",
|
||||||
"drizzle-kit": "^0.30.6",
|
"drizzle-kit": "^0.30.6",
|
||||||
|
|||||||
29
src/pnpm-lock.yaml
generated
29
src/pnpm-lock.yaml
generated
@@ -23,6 +23,9 @@ importers:
|
|||||||
'@nuxtjs/tailwindcss':
|
'@nuxtjs/tailwindcss':
|
||||||
specifier: ^6.13.2
|
specifier: ^6.13.2
|
||||||
version: 6.13.2(magicast@0.3.5)
|
version: 6.13.2(magicast@0.3.5)
|
||||||
|
'@phc/format':
|
||||||
|
specifier: ^1.0.0
|
||||||
|
version: 1.0.0
|
||||||
'@pinia/nuxt':
|
'@pinia/nuxt':
|
||||||
specifier: ^0.10.1
|
specifier: ^0.10.1
|
||||||
version: 0.10.1(magicast@0.3.5)(pinia@3.0.1(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))
|
version: 0.10.1(magicast@0.3.5)(pinia@3.0.1(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2)))
|
||||||
@@ -102,6 +105,9 @@ importers:
|
|||||||
'@types/debug':
|
'@types/debug':
|
||||||
specifier: ^4.1.12
|
specifier: ^4.1.12
|
||||||
version: 4.1.12
|
version: 4.1.12
|
||||||
|
'@types/phc__format':
|
||||||
|
specifier: ^1.0.1
|
||||||
|
version: 1.0.1
|
||||||
'@types/qrcode':
|
'@types/qrcode':
|
||||||
specifier: ^1.5.5
|
specifier: ^1.5.5
|
||||||
version: 1.5.5
|
version: 1.5.5
|
||||||
@@ -1602,11 +1608,11 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1'
|
tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1'
|
||||||
|
|
||||||
'@tanstack/virtual-core@3.13.5':
|
'@tanstack/virtual-core@3.13.6':
|
||||||
resolution: {integrity: sha512-gMLNylxhJdUlfRR1G3U9rtuwUh2IjdrrniJIDcekVJN3/3i+bluvdMi3+eodnxzJq5nKnxnigo9h0lIpaqV6HQ==}
|
resolution: {integrity: sha512-cnQUeWnhNP8tJ4WsGcYiX24Gjkc9ALstLbHcBj1t3E7EimN6n6kHH+DPV4PpDnuw00NApQp+ViojMj1GRdwYQg==}
|
||||||
|
|
||||||
'@tanstack/vue-virtual@3.13.5':
|
'@tanstack/vue-virtual@3.13.6':
|
||||||
resolution: {integrity: sha512-1hhUA6CUjmKc5JDyKLcYOV6mI631FaKKxXh77Ja4UtIy6EOofYaLPk8vVgvK6vLMUSfHR2vI3ZpPY9ibyX60SA==}
|
resolution: {integrity: sha512-GYdZ3SJBQPzgxhuCE2fvpiH46qzHiVx5XzBSdtESgiqh4poj8UgckjGWYEhxaBbcVt1oLzh1m3Ql4TyH32TOzQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vue: ^2.7.0 || ^3.0.0
|
vue: ^2.7.0 || ^3.0.0
|
||||||
|
|
||||||
@@ -1641,6 +1647,9 @@ packages:
|
|||||||
'@types/parse-path@7.0.3':
|
'@types/parse-path@7.0.3':
|
||||||
resolution: {integrity: sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg==}
|
resolution: {integrity: sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg==}
|
||||||
|
|
||||||
|
'@types/phc__format@1.0.1':
|
||||||
|
resolution: {integrity: sha512-hoAQFKcP3voXk/ZEl3jrvS63o/HYLszq4nA2mqjytaSEHEy3j3t0gSFtPLnfKtX34k/xfath7etOoGw5ukoqXQ==}
|
||||||
|
|
||||||
'@types/qrcode@1.5.5':
|
'@types/qrcode@1.5.5':
|
||||||
resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==}
|
resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==}
|
||||||
|
|
||||||
@@ -6664,11 +6673,11 @@ snapshots:
|
|||||||
mini-svg-data-uri: 1.4.4
|
mini-svg-data-uri: 1.4.4
|
||||||
tailwindcss: 3.4.17
|
tailwindcss: 3.4.17
|
||||||
|
|
||||||
'@tanstack/virtual-core@3.13.5': {}
|
'@tanstack/virtual-core@3.13.6': {}
|
||||||
|
|
||||||
'@tanstack/vue-virtual@3.13.5(vue@3.5.13(typescript@5.8.2))':
|
'@tanstack/vue-virtual@3.13.6(vue@3.5.13(typescript@5.8.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tanstack/virtual-core': 3.13.5
|
'@tanstack/virtual-core': 3.13.6
|
||||||
vue: 3.5.13(typescript@5.8.2)
|
vue: 3.5.13(typescript@5.8.2)
|
||||||
|
|
||||||
'@trysound/sax@0.2.0': {}
|
'@trysound/sax@0.2.0': {}
|
||||||
@@ -6698,6 +6707,10 @@ snapshots:
|
|||||||
|
|
||||||
'@types/parse-path@7.0.3': {}
|
'@types/parse-path@7.0.3': {}
|
||||||
|
|
||||||
|
'@types/phc__format@1.0.1':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 22.13.14
|
||||||
|
|
||||||
'@types/qrcode@1.5.5':
|
'@types/qrcode@1.5.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.13.14
|
'@types/node': 22.13.14
|
||||||
@@ -9509,7 +9522,7 @@ snapshots:
|
|||||||
'@floating-ui/vue': 1.1.6(vue@3.5.13(typescript@5.8.2))
|
'@floating-ui/vue': 1.1.6(vue@3.5.13(typescript@5.8.2))
|
||||||
'@internationalized/date': 3.7.0
|
'@internationalized/date': 3.7.0
|
||||||
'@internationalized/number': 3.6.0
|
'@internationalized/number': 3.6.0
|
||||||
'@tanstack/vue-virtual': 3.13.5(vue@3.5.13(typescript@5.8.2))
|
'@tanstack/vue-virtual': 3.13.6(vue@3.5.13(typescript@5.8.2))
|
||||||
'@vueuse/core': 10.11.1(vue@3.5.13(typescript@5.8.2))
|
'@vueuse/core': 10.11.1(vue@3.5.13(typescript@5.8.2))
|
||||||
'@vueuse/shared': 10.11.1(vue@3.5.13(typescript@5.8.2))
|
'@vueuse/shared': 10.11.1(vue@3.5.13(typescript@5.8.2))
|
||||||
aria-hidden: 1.2.4
|
aria-hidden: 1.2.4
|
||||||
|
|||||||
@@ -107,7 +107,15 @@ export class GeneralService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
update(data: GeneralUpdateType) {
|
async update(data: GeneralUpdateType) {
|
||||||
|
// only hash the password if it is not already hashed
|
||||||
|
if (
|
||||||
|
data.metricsPassword !== null &&
|
||||||
|
!isValidPasswordHash(data.metricsPassword)
|
||||||
|
) {
|
||||||
|
data.metricsPassword = await hashPassword(data.metricsPassword);
|
||||||
|
}
|
||||||
|
|
||||||
return this.#db.update(general).set(data).execute();
|
return this.#db.update(general).set(data).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ const metricsEnabled = z.boolean({ message: t('zod.general.metricsEnabled') });
|
|||||||
const metricsPassword = z
|
const metricsPassword = z
|
||||||
.string({ message: t('zod.general.metricsPassword') })
|
.string({ message: t('zod.general.metricsPassword') })
|
||||||
.min(1, { message: t('zod.general.metricsPassword') })
|
.min(1, { message: t('zod.general.metricsPassword') })
|
||||||
// TODO?: validate argon2 regex
|
|
||||||
.nullable();
|
.nullable();
|
||||||
|
|
||||||
export const GeneralUpdateSchema = z.object({
|
export const GeneralUpdateSchema = z.object({
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import argon2 from 'argon2';
|
import argon2 from 'argon2';
|
||||||
|
import { deserialize } from '@phc/format';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if `password` matches the hash.
|
* Checks if `password` matches the hash.
|
||||||
@@ -16,3 +17,21 @@ export function isPasswordValid(
|
|||||||
export async function hashPassword(password: string): Promise<string> {
|
export async function hashPassword(password: string): Promise<string> {
|
||||||
return argon2.hash(password);
|
return argon2.hash(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the password hash is valid.
|
||||||
|
* This only checks if the hash is a valid PHC formatted string using argon2.
|
||||||
|
*/
|
||||||
|
export function isValidPasswordHash(hash: string): boolean {
|
||||||
|
try {
|
||||||
|
const obj = deserialize(hash);
|
||||||
|
|
||||||
|
if (obj.id !== 'argon2i' && obj.id !== 'argon2d' && obj.id !== 'argon2id') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user