From 32b73b850af54ca0d7c9871b1ac24ad4a72387a4 Mon Sep 17 00:00:00 2001 From: Bernd Storath <32197462+kaaax0815@users.noreply.github.com> Date: Tue, 1 Apr 2025 14:43:48 +0200 Subject: [PATCH] Feat: 2fa (#1783) * preplan otp, better qrcode library * add 2fa as feature * add totp generation * working totp lifecycle * don't allow disabled user to log in not a security issue as permission handler would fail anyway * require 2fa on login if enabled * update packages * fix typo * remove console.logs --- CHANGELOG.md | 6 + README.md | 1 + src/app/components/Clients/QRCodeDialog.vue | 4 +- src/app/components/Form/NullTextField.vue | 2 +- src/app/components/Form/TextField.vue | 4 +- src/app/composables/useSubmit.ts | 45 +- src/app/pages/admin/interface.vue | 2 - src/app/pages/login.vue | 51 +- src/app/pages/me.vue | 139 ++++ src/i18n/locales/en.json | 32 +- src/package.json | 8 +- src/pnpm-lock.yaml | 616 +++++++----------- src/server/api/me/totp.post.ts | 65 ++ src/server/api/session.get.ts | 1 + src/server/api/session.post.ts | 49 +- .../database/migrations/0000_short_skin.sql | 2 + .../migrations/meta/0000_snapshot.json | 16 +- .../migrations/meta/0001_snapshot.json | 18 +- .../database/migrations/meta/_journal.json | 4 +- .../database/repositories/user/schema.ts | 2 + .../database/repositories/user/service.ts | 143 ++++ .../database/repositories/user/types.ts | 20 + src/server/utils/WireGuard.ts | 9 +- src/server/utils/types.ts | 7 + 24 files changed, 806 insertions(+), 440 deletions(-) create mode 100644 src/server/api/me/totp.post.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 015cf4f8..f54ce183 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 We're super excited to announce v15! This update is an entire rewrite to make it even easier to set up your own VPN. +## Breaking Changes + +As the whole setup has changed, we recommend to start from scratch. And import your existing configs. + ## Major Changes - Almost all Environment variables removed @@ -26,6 +30,8 @@ This update is an entire rewrite to make it even easier to set up your own VPN. - Removed ARMv6 and ARMv7 support - Connections over HTTP require setting the `INSECURE` env var - Changed license from CC BY-NC-SA 4.0 to AGPL-3.0-only +- Added 2FA using TOTP +- Improved mobile support ## [14.0.0] - 2024-09-04 diff --git a/README.md b/README.md index ec6718e7..3f389b8b 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ You have found the easiest way to install & manage WireGuard on any Linux host! - Prometheus metrics support - IPv6 support - CIDR support +- 2FA support > [!NOTE] > To better manage documentation for this project, it has its own site here: [https://wg-easy.github.io/wg-easy/latest](https://wg-easy.github.io/wg-easy/latest) diff --git a/src/app/components/Clients/QRCodeDialog.vue b/src/app/components/Clients/QRCodeDialog.vue index 7a3e7183..9212619b 100644 --- a/src/app/components/Clients/QRCodeDialog.vue +++ b/src/app/components/Clients/QRCodeDialog.vue @@ -4,7 +4,9 @@ diff --git a/src/app/components/Form/TextField.vue b/src/app/components/Form/TextField.vue index 6cffda28..e62de448 100644 --- a/src/app/components/Form/TextField.vue +++ b/src/app/components/Form/TextField.vue @@ -12,7 +12,8 @@ v-model.trim="data" :name="id" type="text" - :autcomplete="autocomplete" + :autocomplete="autocomplete" + :disabled="disabled" /> @@ -22,6 +23,7 @@ defineProps<{ label: string; description?: string; autocomplete?: string; + disabled?: boolean; }>(); const data = defineModel(); diff --git a/src/app/composables/useSubmit.ts b/src/app/composables/useSubmit.ts index 27d734c1..805c2b66 100644 --- a/src/app/composables/useSubmit.ts +++ b/src/app/composables/useSubmit.ts @@ -1,21 +1,42 @@ -import type { NitroFetchRequest, NitroFetchOptions } from 'nitropack/types'; +import type { + NitroFetchRequest, + NitroFetchOptions, + TypedInternalResponse, + ExtractedRouteMethod, +} from 'nitropack/types'; import { FetchError } from 'ofetch'; -type RevertFn = (success: boolean) => Promise; +type RevertFn< + R extends NitroFetchRequest, + T = unknown, + O extends NitroFetchOptions = NitroFetchOptions, +> = ( + success: boolean, + data: + | TypedInternalResponse< + R, + T, + NitroFetchOptions extends O ? 'get' : ExtractedRouteMethod + > + | undefined +) => Promise; -type SubmitOpts = { - revert: RevertFn; +type SubmitOpts< + R extends NitroFetchRequest, + T = unknown, + O extends NitroFetchOptions = NitroFetchOptions, +> = { + revert: RevertFn; successMsg?: string; - errorMsg?: string; noSuccessToast?: boolean; }; export function useSubmit< R extends NitroFetchRequest, O extends NitroFetchOptions & { body?: never }, ->(url: R, options: O, opts: SubmitOpts) { + T = unknown, +>(url: R, options: O, opts: SubmitOpts) { const toast = useToast(); - const { t: $t } = useI18n(); return async (data: unknown) => { try { @@ -24,11 +45,6 @@ export function useSubmit< body: data, }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if (!(res as any).success) { - throw new Error(opts.errorMsg || $t('toast.errored')); - } - if (!opts.noSuccessToast) { toast.showToast({ type: 'success', @@ -36,7 +52,8 @@ export function useSubmit< }); } - await opts.revert(true); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + await opts.revert(true, res as any); } catch (e) { if (e instanceof FetchError) { toast.showToast({ @@ -51,7 +68,7 @@ export function useSubmit< } else { console.error(e); } - await opts.revert(false); + await opts.revert(false, undefined); } }; } diff --git a/src/app/pages/admin/interface.vue b/src/app/pages/admin/interface.vue index 18988943..55e7d283 100644 --- a/src/app/pages/admin/interface.vue +++ b/src/app/pages/admin/interface.vue @@ -86,7 +86,6 @@ const _changeCidr = useSubmit( { revert, successMsg: t('admin.interface.cidrSuccess'), - errorMsg: t('admin.interface.cidrError'), } ); @@ -102,7 +101,6 @@ const _restartInterface = useSubmit( { revert, successMsg: t('admin.interface.restartSuccess'), - errorMsg: t('admin.interface.restartError'), } ); diff --git a/src/app/pages/login.vue b/src/app/pages/login.vue index 9c342f61..76d34ca7 100644 --- a/src/app/pages/login.vue +++ b/src/app/pages/login.vue @@ -30,6 +30,18 @@ autocomplete="current-password" /> + +