or3-provider-basic-auth 0.0.1

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.
Files changed (73) hide show
  1. package/README.md +99 -0
  2. package/dist/module.d.mts +5 -0
  3. package/dist/module.json +9 -0
  4. package/dist/module.mjs +42 -0
  5. package/dist/runtime/components/BasicAuthChangePasswordModal.client.d.vue.ts +12 -0
  6. package/dist/runtime/components/BasicAuthChangePasswordModal.client.vue +133 -0
  7. package/dist/runtime/components/BasicAuthChangePasswordModal.client.vue.d.ts +12 -0
  8. package/dist/runtime/components/BasicAuthRegisterModal.client.d.vue.ts +11 -0
  9. package/dist/runtime/components/BasicAuthRegisterModal.client.vue +164 -0
  10. package/dist/runtime/components/BasicAuthRegisterModal.client.vue.d.ts +11 -0
  11. package/dist/runtime/components/BasicAuthSignInModal.client.d.vue.ts +13 -0
  12. package/dist/runtime/components/BasicAuthSignInModal.client.vue +136 -0
  13. package/dist/runtime/components/BasicAuthSignInModal.client.vue.d.ts +13 -0
  14. package/dist/runtime/components/BasicAuthUserMenu.client.d.vue.ts +12 -0
  15. package/dist/runtime/components/BasicAuthUserMenu.client.vue +106 -0
  16. package/dist/runtime/components/BasicAuthUserMenu.client.vue.d.ts +12 -0
  17. package/dist/runtime/components/SidebarAuthButtonBasic.client.d.vue.ts +2 -0
  18. package/dist/runtime/components/SidebarAuthButtonBasic.client.vue +154 -0
  19. package/dist/runtime/components/SidebarAuthButtonBasic.client.vue.d.ts +2 -0
  20. package/dist/runtime/lib/constants.d.ts +7 -0
  21. package/dist/runtime/lib/constants.js +7 -0
  22. package/dist/runtime/plugins/auth-status.client.d.ts +2 -0
  23. package/dist/runtime/plugins/auth-status.client.js +38 -0
  24. package/dist/runtime/plugins/basic-auth-ui.client.d.ts +2 -0
  25. package/dist/runtime/plugins/basic-auth-ui.client.js +46 -0
  26. package/dist/runtime/server/admin/adapters/auth-basic-auth.d.ts +2 -0
  27. package/dist/runtime/server/admin/adapters/auth-basic-auth.js +34 -0
  28. package/dist/runtime/server/api/basic-auth/_helpers.d.ts +6 -0
  29. package/dist/runtime/server/api/basic-auth/_helpers.js +26 -0
  30. package/dist/runtime/server/api/basic-auth/change-password.post.d.ts +8 -0
  31. package/dist/runtime/server/api/basic-auth/change-password.post.js +49 -0
  32. package/dist/runtime/server/api/basic-auth/refresh.post.d.ts +8 -0
  33. package/dist/runtime/server/api/basic-auth/refresh.post.js +78 -0
  34. package/dist/runtime/server/api/basic-auth/register.post.d.ts +8 -0
  35. package/dist/runtime/server/api/basic-auth/register.post.js +112 -0
  36. package/dist/runtime/server/api/basic-auth/sign-in.post.d.ts +8 -0
  37. package/dist/runtime/server/api/basic-auth/sign-in.post.js +75 -0
  38. package/dist/runtime/server/api/basic-auth/sign-out.post.d.ts +8 -0
  39. package/dist/runtime/server/api/basic-auth/sign-out.post.js +37 -0
  40. package/dist/runtime/server/auth/basic-auth-provider.d.ts +2 -0
  41. package/dist/runtime/server/auth/basic-auth-provider.js +41 -0
  42. package/dist/runtime/server/auth/index.d.ts +1 -0
  43. package/dist/runtime/server/auth/index.js +1 -0
  44. package/dist/runtime/server/db/client.d.ts +3 -0
  45. package/dist/runtime/server/db/client.js +106 -0
  46. package/dist/runtime/server/db/schema.d.ts +21 -0
  47. package/dist/runtime/server/db/schema.js +0 -0
  48. package/dist/runtime/server/lib/config.d.ts +22 -0
  49. package/dist/runtime/server/lib/config.js +94 -0
  50. package/dist/runtime/server/lib/cookies.d.ts +4 -0
  51. package/dist/runtime/server/lib/cookies.js +42 -0
  52. package/dist/runtime/server/lib/errors.d.ts +4 -0
  53. package/dist/runtime/server/lib/errors.js +25 -0
  54. package/dist/runtime/server/lib/jwt.d.ts +37 -0
  55. package/dist/runtime/server/lib/jwt.js +77 -0
  56. package/dist/runtime/server/lib/password.d.ts +2 -0
  57. package/dist/runtime/server/lib/password.js +8 -0
  58. package/dist/runtime/server/lib/rate-limit.d.ts +6 -0
  59. package/dist/runtime/server/lib/rate-limit.js +163 -0
  60. package/dist/runtime/server/lib/request-identity.d.ts +16 -0
  61. package/dist/runtime/server/lib/request-identity.js +32 -0
  62. package/dist/runtime/server/lib/request-security.d.ts +2 -0
  63. package/dist/runtime/server/lib/request-security.js +37 -0
  64. package/dist/runtime/server/lib/session-store.d.ts +46 -0
  65. package/dist/runtime/server/lib/session-store.js +190 -0
  66. package/dist/runtime/server/plugins/register.d.ts +2 -0
  67. package/dist/runtime/server/plugins/register.js +63 -0
  68. package/dist/runtime/server/token-broker/basic-auth-token-broker.d.ts +6 -0
  69. package/dist/runtime/server/token-broker/basic-auth-token-broker.js +8 -0
  70. package/dist/runtime/server/token-broker/index.d.ts +1 -0
  71. package/dist/runtime/server/token-broker/index.js +1 -0
  72. package/dist/types.d.mts +7 -0
  73. package/package.json +70 -0
@@ -0,0 +1,106 @@
1
+ <template>
2
+ <UPopover
3
+ v-model:open="isOpen"
4
+ portal
5
+ :content="{ side: 'right', align: 'end', sideOffset: 10 }"
6
+ :ui="{ content: 'z-[260] max-w-[240px] p-0 border-none bg-transparent shadow-none' }"
7
+ >
8
+ <UButton
9
+ v-bind="triggerButtonProps"
10
+ type="button"
11
+ aria-label="Account menu"
12
+ >
13
+ <template #default>
14
+ <span class="flex flex-col items-center gap-1 w-full">
15
+ <UIcon name="i-lucide-user-check" class="h-[18px] w-[18px]" />
16
+ <span class="text-[7px] uppercase tracking-wider whitespace-nowrap">
17
+ Account
18
+ </span>
19
+ </span>
20
+ </template>
21
+ </UButton>
22
+
23
+ <template #content>
24
+ <div
25
+ class="w-[220px] rounded-[var(--md-border-radius)] border-[length:var(--md-border-width)] border-[color:var(--md-border-color)] bg-[var(--md-surface)] overflow-hidden theme-shadow"
26
+ >
27
+ <!-- User identity section -->
28
+ <div class="px-4 pt-4 pb-3">
29
+ <div class="flex items-center gap-3">
30
+ <div class="flex items-center justify-center w-8 h-8 rounded-full bg-[var(--md-success)]/15 text-[var(--md-success)] shrink-0">
31
+ <UIcon name="i-lucide-user" class="w-4 h-4" />
32
+ </div>
33
+ <div class="min-w-0">
34
+ <p class="text-[10px] uppercase tracking-wider text-[var(--md-on-surface)]/50 mb-0.5">
35
+ Signed in as
36
+ </p>
37
+ <p class="text-xs font-medium text-[var(--md-on-surface)] break-all leading-tight">
38
+ {{ email }}
39
+ </p>
40
+ </div>
41
+ </div>
42
+ </div>
43
+
44
+ <div class="h-[var(--md-border-width)] bg-[var(--md-border-color)] mx-3" />
45
+
46
+ <!-- Actions -->
47
+ <div class="p-2 space-y-1">
48
+ <button
49
+ type="button"
50
+ class="w-full flex items-center gap-2.5 px-3 py-2 text-sm text-[var(--md-on-surface)] rounded-[var(--md-border-radius)] hover:bg-[var(--md-surface-hover)] active:bg-[var(--md-surface-active)] transition-colors text-left"
51
+ @click="onChangePassword"
52
+ >
53
+ <UIcon name="i-lucide-key-round" class="w-4 h-4 text-[var(--md-on-surface)]/60 shrink-0" />
54
+ Change Password
55
+ </button>
56
+
57
+ <button
58
+ type="button"
59
+ :disabled="pending"
60
+ class="w-full flex items-center gap-2.5 px-3 py-2 text-sm rounded-[var(--md-border-radius)] text-[var(--md-error)] hover:bg-[var(--md-error)]/10 active:bg-[var(--md-error)]/18 transition-colors text-left disabled:opacity-50"
61
+ @click="onSignOut"
62
+ >
63
+ <UIcon name="i-lucide-log-out" class="w-4 h-4 shrink-0" />
64
+ {{ pending ? "Signing out\u2026" : "Sign Out" }}
65
+ </button>
66
+ </div>
67
+ </div>
68
+ </template>
69
+ </UPopover>
70
+ </template>
71
+
72
+ <script setup>
73
+ import { ref } from "vue";
74
+ defineProps({
75
+ email: { type: String, required: false },
76
+ displayName: { type: String, required: false }
77
+ });
78
+ const emit = defineEmits(["signed-out", "change-password"]);
79
+ const isOpen = ref(false);
80
+ const pending = ref(false);
81
+ const triggerButtonProps = {
82
+ block: true,
83
+ variant: "ghost",
84
+ color: "neutral",
85
+ class: "theme-btn h-[48px] w-[48px] p-0! flex flex-col items-center justify-center gap-1 py-1.5 bg-transparent border-[length:var(--md-border-width)] border-[color:var(--md-success)]/40 rounded-[var(--md-border-radius)] text-[var(--md-on-surface)] hover:bg-[var(--md-success)]/18 active:bg-[var(--md-success)]/28 transition-colors duration-150 shadow-none",
86
+ ui: {
87
+ base: "justify-center shadow-none"
88
+ }
89
+ };
90
+ function onChangePassword() {
91
+ isOpen.value = false;
92
+ emit("change-password");
93
+ }
94
+ async function onSignOut() {
95
+ pending.value = true;
96
+ try {
97
+ await $fetch("/api/basic-auth/sign-out", {
98
+ method: "POST"
99
+ });
100
+ emit("signed-out");
101
+ } finally {
102
+ pending.value = false;
103
+ isOpen.value = false;
104
+ }
105
+ }
106
+ </script>
@@ -0,0 +1,12 @@
1
+ type __VLS_Props = {
2
+ email?: string;
3
+ displayName?: string;
4
+ };
5
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
6
+ "signed-out": () => any;
7
+ "change-password": () => any;
8
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
9
+ "onSigned-out"?: (() => any) | undefined;
10
+ "onChange-password"?: (() => any) | undefined;
11
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
12
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ export default _default;
@@ -0,0 +1,154 @@
1
+ <template>
2
+ <template v-if="isBasicAuthProvider">
3
+ <BasicAuthUserMenu
4
+ v-if="isSignedIn"
5
+ :email="sessionUser?.email"
6
+ :display-name="sessionUser?.displayName"
7
+ @signed-out="onSignedOut"
8
+ @change-password="changePasswordModalOpen = true"
9
+ />
10
+
11
+ <UButton
12
+ v-else
13
+ v-bind="loginButtonProps"
14
+ type="button"
15
+ aria-label="Login"
16
+ @click="signInModalOpen = true"
17
+ >
18
+ <template #default>
19
+ <span class="flex flex-col items-center gap-1 w-full">
20
+ <UIcon name="i-lucide-log-in" class="h-[18px] w-[18px]" />
21
+ <span class="text-[7px] uppercase tracking-wider whitespace-nowrap">
22
+ Login
23
+ </span>
24
+ </span>
25
+ </template>
26
+ </UButton>
27
+
28
+ <BasicAuthSignInModal
29
+ v-model="signInModalOpen"
30
+ @signed-in="onSignedIn"
31
+ @open-register="openRegisterFromSignIn"
32
+ />
33
+
34
+ <BasicAuthRegisterModal
35
+ v-model="registerModalOpen"
36
+ @registered="onRegistered"
37
+ />
38
+
39
+ <BasicAuthChangePasswordModal
40
+ v-model="changePasswordModalOpen"
41
+ :username="sessionUser?.email"
42
+ @updated="onPasswordUpdated"
43
+ />
44
+ </template>
45
+ </template>
46
+
47
+ <script setup>
48
+ import { computed, nextTick, onMounted, ref } from "vue";
49
+ import { BASIC_AUTH_PROVIDER_ID } from "../lib/constants";
50
+ import BasicAuthSignInModal from "./BasicAuthSignInModal.client.vue";
51
+ import BasicAuthRegisterModal from "./BasicAuthRegisterModal.client.vue";
52
+ import BasicAuthUserMenu from "./BasicAuthUserMenu.client.vue";
53
+ import BasicAuthChangePasswordModal from "./BasicAuthChangePasswordModal.client.vue";
54
+ const runtimeConfig = useRuntimeConfig();
55
+ const signInModalOpen = ref(false);
56
+ const registerModalOpen = ref(false);
57
+ const changePasswordModalOpen = ref(false);
58
+ let refreshRequest = null;
59
+ const session = ref(null);
60
+ async function fetchSessionPayload() {
61
+ return await $fetch("/api/auth/session", {
62
+ cache: "no-store"
63
+ });
64
+ }
65
+ async function tryRefreshTokens() {
66
+ if (refreshRequest) {
67
+ return refreshRequest;
68
+ }
69
+ refreshRequest = (async () => {
70
+ try {
71
+ const response = await $fetch("/api/basic-auth/refresh?silent=1", {
72
+ method: "POST"
73
+ });
74
+ return response?.ok === true;
75
+ } catch {
76
+ return false;
77
+ }
78
+ })().finally(() => {
79
+ refreshRequest = null;
80
+ });
81
+ return refreshRequest;
82
+ }
83
+ async function refreshSession(options = {}) {
84
+ const allowSilentRefresh = options.allowSilentRefresh ?? true;
85
+ try {
86
+ const payload = await fetchSessionPayload();
87
+ session.value = payload.session ?? null;
88
+ } catch {
89
+ session.value = null;
90
+ return;
91
+ }
92
+ if (session.value || !allowSilentRefresh) {
93
+ return;
94
+ }
95
+ const didRefresh = await tryRefreshTokens();
96
+ if (!didRefresh) {
97
+ return;
98
+ }
99
+ try {
100
+ const payload = await fetchSessionPayload();
101
+ session.value = payload.session ?? null;
102
+ } catch {
103
+ session.value = null;
104
+ }
105
+ }
106
+ const isBasicAuthProvider = computed(() => {
107
+ const publicProvider = runtimeConfig.public?.authProvider;
108
+ if (!publicProvider) return true;
109
+ return publicProvider === BASIC_AUTH_PROVIDER_ID;
110
+ });
111
+ const isSignedIn = computed(
112
+ () => session.value?.authenticated === true && session.value.provider === BASIC_AUTH_PROVIDER_ID
113
+ );
114
+ const sessionUser = computed(() => session.value?.user);
115
+ const loginButtonProps = {
116
+ block: true,
117
+ variant: "ghost",
118
+ color: "neutral",
119
+ class: "theme-btn h-[48px] w-[48px] p-0! flex flex-col items-center justify-center gap-1 py-1.5 bg-transparent border-[length:var(--md-border-width)] border-[color:var(--md-primary)]/30 rounded-[var(--md-border-radius)] text-[var(--md-primary)] hover:bg-[var(--md-primary)]/15 active:bg-[var(--md-primary)]/25 transition-colors duration-150 shadow-none",
120
+ ui: {
121
+ base: "justify-center shadow-none"
122
+ }
123
+ };
124
+ function notifyAuthSessionChanged() {
125
+ if (typeof window === "undefined") return;
126
+ window.dispatchEvent(new CustomEvent("or3:auth-session-changed"));
127
+ }
128
+ async function onSignedIn() {
129
+ await refreshSession({ allowSilentRefresh: false });
130
+ notifyAuthSessionChanged();
131
+ }
132
+ async function onSignedOut() {
133
+ await refreshSession({ allowSilentRefresh: false });
134
+ notifyAuthSessionChanged();
135
+ }
136
+ async function onPasswordUpdated() {
137
+ await refreshSession({ allowSilentRefresh: false });
138
+ notifyAuthSessionChanged();
139
+ }
140
+ async function openRegisterFromSignIn() {
141
+ signInModalOpen.value = false;
142
+ await nextTick();
143
+ setTimeout(() => {
144
+ registerModalOpen.value = true;
145
+ }, 0);
146
+ }
147
+ async function onRegistered() {
148
+ await refreshSession({ allowSilentRefresh: false });
149
+ notifyAuthSessionChanged();
150
+ }
151
+ onMounted(() => {
152
+ void refreshSession();
153
+ });
154
+ </script>
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ export default _default;
@@ -0,0 +1,7 @@
1
+ export declare const BASIC_AUTH_PROVIDER_ID = "basic-auth";
2
+ export declare const ACCESS_COOKIE_NAME = "or3_access";
3
+ export declare const REFRESH_COOKIE_NAME = "or3_refresh";
4
+ export declare const DEFAULT_ACCESS_TTL_SECONDS = 900;
5
+ export declare const DEFAULT_REFRESH_TTL_SECONDS: number;
6
+ export declare const ACCESS_COOKIE_PATH = "/";
7
+ export declare const REFRESH_COOKIE_PATH = "/api/basic-auth";
@@ -0,0 +1,7 @@
1
+ export const BASIC_AUTH_PROVIDER_ID = "basic-auth";
2
+ export const ACCESS_COOKIE_NAME = "or3_access";
3
+ export const REFRESH_COOKIE_NAME = "or3_refresh";
4
+ export const DEFAULT_ACCESS_TTL_SECONDS = 900;
5
+ export const DEFAULT_REFRESH_TTL_SECONDS = 60 * 60 * 24 * 30;
6
+ export const ACCESS_COOKIE_PATH = "/";
7
+ export const REFRESH_COOKIE_PATH = "/api/basic-auth";
@@ -0,0 +1,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -0,0 +1,38 @@
1
+ import { registerClientAuthStatusResolver } from "~/composables/auth/useClientAuthStatus.client";
2
+ async function fetchSession() {
3
+ try {
4
+ return await $fetch("/api/auth/session", {
5
+ cache: "no-store"
6
+ });
7
+ } catch {
8
+ return null;
9
+ }
10
+ }
11
+ async function trySilentRefresh() {
12
+ try {
13
+ const res = await $fetch("/api/basic-auth/refresh?silent=1", {
14
+ method: "POST"
15
+ });
16
+ return res?.ok === true;
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
21
+ export default defineNuxtPlugin(() => {
22
+ if (import.meta.server) return;
23
+ registerClientAuthStatusResolver(async () => {
24
+ const first = await fetchSession();
25
+ const firstSession = first?.session;
26
+ if (firstSession?.authenticated === true && firstSession.provider === "basic-auth") {
27
+ return { ready: true, authenticated: true };
28
+ }
29
+ const refreshed = await trySilentRefresh();
30
+ if (!refreshed) {
31
+ return { ready: true, authenticated: false };
32
+ }
33
+ const second = await fetchSession();
34
+ const secondSession = second?.session;
35
+ const authenticated = secondSession?.authenticated === true && secondSession.provider === "basic-auth";
36
+ return { ready: true, authenticated };
37
+ });
38
+ });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
2
+ export default _default;
@@ -0,0 +1,46 @@
1
+ import { defineNuxtPlugin, useNuxtApp, useRuntimeConfig } from "#imports";
2
+ import { BASIC_AUTH_PROVIDER_ID } from "../lib/constants.js";
3
+ function tryRegisterAuthUiAdapter(component) {
4
+ const nuxtApp = useNuxtApp();
5
+ if (typeof nuxtApp.$registerAuthUiAdapter !== "function") {
6
+ return false;
7
+ }
8
+ nuxtApp.$registerAuthUiAdapter({
9
+ id: BASIC_AUTH_PROVIDER_ID,
10
+ component
11
+ });
12
+ return true;
13
+ }
14
+ function enqueueAuthUiAdapter(component) {
15
+ const payload = {
16
+ id: BASIC_AUTH_PROVIDER_ID,
17
+ component
18
+ };
19
+ const globalState = globalThis;
20
+ if (!Array.isArray(globalState.__or3AuthUiAdapterQueue__)) {
21
+ globalState.__or3AuthUiAdapterQueue__ = [];
22
+ }
23
+ globalState.__or3AuthUiAdapterQueue__.push(payload);
24
+ if (typeof window !== "undefined") {
25
+ window.dispatchEvent(
26
+ new CustomEvent("or3:auth-ui-adapter-register", {
27
+ detail: payload
28
+ })
29
+ );
30
+ }
31
+ }
32
+ export default defineNuxtPlugin(async () => {
33
+ if (import.meta.server) return;
34
+ const runtimeConfig = useRuntimeConfig();
35
+ if (!runtimeConfig.public?.ssrAuthEnabled) return;
36
+ const publicProviderId = runtimeConfig.public?.authProvider;
37
+ if (publicProviderId && publicProviderId !== BASIC_AUTH_PROVIDER_ID) {
38
+ return;
39
+ }
40
+ const componentModule = await import("../components/SidebarAuthButtonBasic.client.vue");
41
+ if (componentModule.default) {
42
+ if (!tryRegisterAuthUiAdapter(componentModule.default)) {
43
+ enqueueAuthUiAdapter(componentModule.default);
44
+ }
45
+ }
46
+ });
@@ -0,0 +1,2 @@
1
+ import type { ProviderAdminAdapter } from '~~/server/admin/providers/types';
2
+ export declare const basicAuthAdminAdapter: ProviderAdminAdapter;
@@ -0,0 +1,34 @@
1
+ import { BASIC_AUTH_PROVIDER_ID } from "../../../lib/constants.js";
2
+ import { validateBasicAuthConfig } from "../../lib/config.js";
3
+ export const basicAuthAdminAdapter = {
4
+ id: BASIC_AUTH_PROVIDER_ID,
5
+ kind: "auth",
6
+ async getStatus(_event, ctx) {
7
+ const diagnostics = validateBasicAuthConfig(useRuntimeConfig());
8
+ const warnings = [];
9
+ for (const message of diagnostics.warnings) {
10
+ warnings.push({ level: "warning", message });
11
+ }
12
+ for (const message of diagnostics.errors) {
13
+ warnings.push({ level: "error", message });
14
+ }
15
+ if (ctx.enabled && !diagnostics.isValid) {
16
+ warnings.push({
17
+ level: "error",
18
+ message: "Basic-auth configuration is invalid. Authentication may fail."
19
+ });
20
+ }
21
+ return {
22
+ details: {
23
+ dbPath: diagnostics.config.dbPath,
24
+ jwtSecretConfigured: Boolean(diagnostics.config.jwtSecret),
25
+ refreshSecretConfigured: Boolean(diagnostics.config.refreshSecret),
26
+ bootstrapConfigured: Boolean(diagnostics.config.bootstrapEmail) && Boolean(diagnostics.config.bootstrapPassword),
27
+ accessTtlSeconds: diagnostics.config.accessTtlSeconds,
28
+ refreshTtlSeconds: diagnostics.config.refreshTtlSeconds
29
+ },
30
+ warnings,
31
+ actions: []
32
+ };
33
+ }
34
+ };
@@ -0,0 +1,6 @@
1
+ import { createError, type H3Event } from 'h3';
2
+ import type { z } from 'zod';
3
+ export declare function assertBasicAuthReady(event: H3Event): void;
4
+ export declare function parseBodyWithSchema<T extends z.ZodTypeAny>(event: H3Event, schema: T): Promise<z.infer<T>>;
5
+ export declare function noStore(event: H3Event): void;
6
+ export declare function createForbiddenError(): ReturnType<typeof createError>;
@@ -0,0 +1,26 @@
1
+ import { createError, readBody, setResponseHeader } from "h3";
2
+ import { validateBasicAuthConfig } from "../../lib/config.js";
3
+ import { createAuthNotConfiguredError, createInvalidRequestError } from "../../lib/errors.js";
4
+ export function assertBasicAuthReady(event) {
5
+ const diagnostics = validateBasicAuthConfig(useRuntimeConfig(event));
6
+ if (!diagnostics.config.enabled || diagnostics.config.providerId !== "basic-auth") {
7
+ throw createAuthNotConfiguredError();
8
+ }
9
+ if (!diagnostics.isValid) {
10
+ throw createAuthNotConfiguredError();
11
+ }
12
+ }
13
+ export async function parseBodyWithSchema(event, schema) {
14
+ const body = await readBody(event);
15
+ const result = schema.safeParse(body);
16
+ if (!result.success) {
17
+ throw createInvalidRequestError();
18
+ }
19
+ return result.data;
20
+ }
21
+ export function noStore(event) {
22
+ setResponseHeader(event, "Cache-Control", "no-store");
23
+ }
24
+ export function createForbiddenError() {
25
+ return createError({ statusCode: 403, statusMessage: "Forbidden" });
26
+ }
@@ -0,0 +1,8 @@
1
+ import { type H3Event } from 'h3';
2
+ export declare function handleChangePassword(event: H3Event): Promise<{
3
+ ok: boolean;
4
+ }>;
5
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
6
+ ok: boolean;
7
+ }>>;
8
+ export default _default;
@@ -0,0 +1,49 @@
1
+ import { defineEventHandler } from "h3";
2
+ import { z } from "zod";
3
+ import { clearAuthCookies } from "../../lib/cookies.js";
4
+ import { createInvalidCredentialsError, createSessionExpiredError } from "../../lib/errors.js";
5
+ import { hashPassword, verifyPassword } from "../../lib/password.js";
6
+ import { enforceBasicAuthRateLimit } from "../../lib/rate-limit.js";
7
+ import {
8
+ findAccountById,
9
+ updatePasswordAndRevokeSessions
10
+ } from "../../lib/session-store.js";
11
+ import { enforceMutationOriginPolicy } from "../../lib/request-security.js";
12
+ import { basicAuthProvider } from "../../auth/basic-auth-provider.js";
13
+ import { assertBasicAuthReady, noStore, parseBodyWithSchema } from "./_helpers.js";
14
+ const changePasswordSchema = z.object({
15
+ // Existing accounts may have legacy short passwords; only new password is length-enforced.
16
+ currentPassword: z.string().min(1).max(512),
17
+ newPassword: z.string().min(8).max(512),
18
+ confirmNewPassword: z.string().min(8).max(512)
19
+ }).refine((input) => input.newPassword === input.confirmNewPassword, {
20
+ message: "Passwords must match"
21
+ });
22
+ export async function handleChangePassword(event) {
23
+ assertBasicAuthReady(event);
24
+ noStore(event);
25
+ enforceMutationOriginPolicy(event);
26
+ enforceBasicAuthRateLimit(event, "basic-auth:change-password");
27
+ const session = await basicAuthProvider.getSession(event);
28
+ if (!session) {
29
+ clearAuthCookies(event);
30
+ throw createSessionExpiredError();
31
+ }
32
+ const body = await parseBodyWithSchema(event, changePasswordSchema);
33
+ const account = findAccountById(session.user.id);
34
+ if (!account) {
35
+ clearAuthCookies(event);
36
+ throw createSessionExpiredError();
37
+ }
38
+ const validCurrentPassword = await verifyPassword(body.currentPassword, account.password_hash);
39
+ if (!validCurrentPassword) {
40
+ throw createInvalidCredentialsError();
41
+ }
42
+ const newPasswordHash = await hashPassword(body.newPassword);
43
+ updatePasswordAndRevokeSessions(account.id, newPasswordHash);
44
+ clearAuthCookies(event);
45
+ return { ok: true };
46
+ }
47
+ export default defineEventHandler(async (event) => {
48
+ return await handleChangePassword(event);
49
+ });
@@ -0,0 +1,8 @@
1
+ import { type H3Event } from 'h3';
2
+ export declare function handleRefresh(event: H3Event): Promise<{
3
+ ok: boolean;
4
+ }>;
5
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
6
+ ok: boolean;
7
+ }>>;
8
+ export default _default;
@@ -0,0 +1,78 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { defineEventHandler, getQuery } from "h3";
3
+ import { clearAuthCookies, setAccessCookie, setRefreshCookie } from "../../lib/cookies.js";
4
+ import { getBasicAuthConfig } from "../../lib/config.js";
5
+ import { createSessionExpiredError } from "../../lib/errors.js";
6
+ import {
7
+ getRefreshTokenFromEvent,
8
+ hashRefreshToken,
9
+ signAccessToken,
10
+ signRefreshToken,
11
+ verifyRefreshToken
12
+ } from "../../lib/jwt.js";
13
+ import { enforceBasicAuthRateLimit } from "../../lib/rate-limit.js";
14
+ import {
15
+ findAccountById,
16
+ rotateSession,
17
+ getSessionMetadataFromEvent
18
+ } from "../../lib/session-store.js";
19
+ import { enforceMutationOriginPolicy } from "../../lib/request-security.js";
20
+ import { assertBasicAuthReady, noStore } from "./_helpers.js";
21
+ export async function handleRefresh(event) {
22
+ assertBasicAuthReady(event);
23
+ noStore(event);
24
+ enforceMutationOriginPolicy(event);
25
+ enforceBasicAuthRateLimit(event, "basic-auth:refresh");
26
+ const query = getQuery(event);
27
+ const silent = query.silent === "1" || query.silent === "true";
28
+ const failExpired = () => {
29
+ clearAuthCookies(event);
30
+ if (silent) {
31
+ return { ok: false };
32
+ }
33
+ throw createSessionExpiredError();
34
+ };
35
+ const refreshToken = getRefreshTokenFromEvent(event);
36
+ if (!refreshToken) {
37
+ return failExpired();
38
+ }
39
+ const claims = await verifyRefreshToken(refreshToken);
40
+ if (!claims) {
41
+ return failExpired();
42
+ }
43
+ const account = findAccountById(claims.sub);
44
+ if (!account || account.token_version !== claims.ver) {
45
+ return failExpired();
46
+ }
47
+ const config = getBasicAuthConfig();
48
+ const newSessionId = randomUUID();
49
+ const newRefreshToken = await signRefreshToken({
50
+ sub: account.id,
51
+ sid: newSessionId,
52
+ ver: account.token_version
53
+ });
54
+ const rotation = rotateSession({
55
+ currentSessionId: claims.sid,
56
+ currentRefreshHash: hashRefreshToken(refreshToken),
57
+ newSessionId,
58
+ newRefreshHash: hashRefreshToken(newRefreshToken),
59
+ newExpiresAtMs: Date.now() + config.refreshTtlSeconds * 1e3,
60
+ metadata: getSessionMetadataFromEvent(event)
61
+ });
62
+ if (!rotation.ok || rotation.accountId !== account.id) {
63
+ return failExpired();
64
+ }
65
+ const accessToken = await signAccessToken({
66
+ sub: account.id,
67
+ sid: newSessionId,
68
+ ver: account.token_version,
69
+ email: account.email,
70
+ display_name: account.display_name
71
+ });
72
+ setAccessCookie(event, accessToken, config.accessTtlSeconds);
73
+ setRefreshCookie(event, newRefreshToken, config.refreshTtlSeconds);
74
+ return { ok: true };
75
+ }
76
+ export default defineEventHandler(async (event) => {
77
+ return await handleRefresh(event);
78
+ });
@@ -0,0 +1,8 @@
1
+ import { type H3Event } from 'h3';
2
+ export declare function handleRegister(event: H3Event): Promise<{
3
+ ok: boolean;
4
+ }>;
5
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
6
+ ok: boolean;
7
+ }>>;
8
+ export default _default;