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,112 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { createError, defineEventHandler, setCookie } from "h3";
3
+ import { z } from "zod";
4
+ import { clearAuthCookies, setAccessCookie, setRefreshCookie } from "../../lib/cookies.js";
5
+ import { getBasicAuthConfig } from "../../lib/config.js";
6
+ import {
7
+ createInvalidRequestError,
8
+ createInvalidCredentialsError
9
+ } from "../../lib/errors.js";
10
+ import { signAccessToken, signRefreshToken, hashRefreshToken } from "../../lib/jwt.js";
11
+ import { hashPassword } from "../../lib/password.js";
12
+ import { enforceBasicAuthRateLimit } from "../../lib/rate-limit.js";
13
+ import {
14
+ createAccount,
15
+ createAuthSession,
16
+ findAccountByEmail,
17
+ normalizeEmail,
18
+ getSessionMetadataFromEvent
19
+ } from "../../lib/session-store.js";
20
+ import { enforceMutationOriginPolicy } from "../../lib/request-security.js";
21
+ import { assertBasicAuthReady, noStore, parseBodyWithSchema } from "./_helpers.js";
22
+ const registerSchema = z.object({
23
+ email: z.string().email().max(320),
24
+ password: z.string().min(8).max(512),
25
+ confirmPassword: z.string().min(8).max(512),
26
+ displayName: z.string().trim().min(1).max(120).optional(),
27
+ inviteToken: z.string().trim().min(1).max(4096).optional()
28
+ }).refine((value) => value.password === value.confirmPassword, {
29
+ message: "Passwords must match"
30
+ });
31
+ function mapRegistrationErrorToHttp(reason) {
32
+ if (reason === "disabled") {
33
+ throw createError({
34
+ statusCode: 403,
35
+ statusMessage: "Registration is currently disabled. Please contact an administrator."
36
+ });
37
+ }
38
+ throw createError({
39
+ statusCode: 403,
40
+ statusMessage: "A valid invite is required to register."
41
+ });
42
+ }
43
+ export async function handleRegister(event) {
44
+ assertBasicAuthReady(event);
45
+ noStore(event);
46
+ enforceMutationOriginPolicy(event);
47
+ enforceBasicAuthRateLimit(event, "basic-auth:register");
48
+ const body = await parseBodyWithSchema(event, registerSchema);
49
+ const email = normalizeEmail(body.email);
50
+ const existing = findAccountByEmail(email);
51
+ if (existing) {
52
+ throw createInvalidCredentialsError();
53
+ }
54
+ const runtimeConfig = useRuntimeConfig();
55
+ const mode = runtimeConfig.auth?.registrationMode ?? (runtimeConfig.auth?.autoProvision === false ? "disabled" : "open");
56
+ if (mode === "disabled") {
57
+ mapRegistrationErrorToHttp("disabled");
58
+ }
59
+ if (mode === "invite_only" && !body.inviteToken?.trim()) {
60
+ mapRegistrationErrorToHttp("invite_required");
61
+ }
62
+ const passwordHash = await hashPassword(body.password);
63
+ const account = createAccount({
64
+ email,
65
+ passwordHash,
66
+ displayName: body.displayName?.trim() || null
67
+ });
68
+ const config = getBasicAuthConfig();
69
+ if (config.refreshTtlSeconds <= 0 || config.accessTtlSeconds <= 0) {
70
+ throw createInvalidRequestError();
71
+ }
72
+ const sessionId = randomUUID();
73
+ const refreshToken = await signRefreshToken({
74
+ sub: account.id,
75
+ sid: sessionId,
76
+ ver: account.token_version
77
+ });
78
+ createAuthSession({
79
+ accountId: account.id,
80
+ sessionId,
81
+ refreshTokenHash: hashRefreshToken(refreshToken),
82
+ expiresAtMs: Date.now() + config.refreshTtlSeconds * 1e3,
83
+ metadata: getSessionMetadataFromEvent(event)
84
+ });
85
+ const accessToken = await signAccessToken({
86
+ sub: account.id,
87
+ sid: sessionId,
88
+ ver: account.token_version,
89
+ email: account.email,
90
+ display_name: account.display_name
91
+ });
92
+ setAccessCookie(event, accessToken, config.accessTtlSeconds);
93
+ setRefreshCookie(event, refreshToken, config.refreshTtlSeconds);
94
+ if (mode === "invite_only" && body.inviteToken?.trim()) {
95
+ setCookie(event, "or3_invite_token", body.inviteToken.trim(), {
96
+ httpOnly: true,
97
+ sameSite: "lax",
98
+ secure: process.env.NODE_ENV === "production",
99
+ path: "/",
100
+ maxAge: 10 * 60
101
+ });
102
+ }
103
+ return { ok: true };
104
+ }
105
+ export default defineEventHandler(async (event) => {
106
+ try {
107
+ return await handleRegister(event);
108
+ } catch (error) {
109
+ clearAuthCookies(event);
110
+ throw error;
111
+ }
112
+ });
@@ -0,0 +1,8 @@
1
+ import { type H3Event } from 'h3';
2
+ export declare function handleSignIn(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,75 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { defineEventHandler } from "h3";
3
+ import { z } from "zod";
4
+ import { clearAuthCookies, setAccessCookie, setRefreshCookie } from "../../lib/cookies.js";
5
+ import { getBasicAuthConfig } from "../../lib/config.js";
6
+ import {
7
+ createInvalidCredentialsError,
8
+ createInvalidRequestError
9
+ } from "../../lib/errors.js";
10
+ import { signAccessToken, signRefreshToken, hashRefreshToken } from "../../lib/jwt.js";
11
+ import { verifyPassword } from "../../lib/password.js";
12
+ import { enforceBasicAuthRateLimit } from "../../lib/rate-limit.js";
13
+ import {
14
+ createAuthSession,
15
+ findAccountByEmail,
16
+ normalizeEmail,
17
+ getSessionMetadataFromEvent
18
+ } from "../../lib/session-store.js";
19
+ import { enforceMutationOriginPolicy } from "../../lib/request-security.js";
20
+ import { assertBasicAuthReady, noStore, parseBodyWithSchema } from "./_helpers.js";
21
+ const signInSchema = z.object({
22
+ email: z.string().email().max(320),
23
+ // Sign-in must allow legacy short passwords; strength is enforced at set/change time.
24
+ password: z.string().min(1).max(512)
25
+ });
26
+ const DUMMY_PASSWORD_HASH = "$2a$12$1XCXpgmbzURDdc0AGJiAsemtT39PAw8WwV7fBOq6A5VQv6.qN6Va.";
27
+ export async function handleSignIn(event) {
28
+ assertBasicAuthReady(event);
29
+ noStore(event);
30
+ enforceMutationOriginPolicy(event);
31
+ enforceBasicAuthRateLimit(event, "basic-auth:sign-in");
32
+ const input = await parseBodyWithSchema(event, signInSchema);
33
+ const email = normalizeEmail(input.email);
34
+ const account = findAccountByEmail(email);
35
+ if (!account) {
36
+ await verifyPassword(input.password, DUMMY_PASSWORD_HASH);
37
+ clearAuthCookies(event);
38
+ throw createInvalidCredentialsError();
39
+ }
40
+ const isPasswordValid = await verifyPassword(input.password, account.password_hash);
41
+ if (!isPasswordValid) {
42
+ clearAuthCookies(event);
43
+ throw createInvalidCredentialsError();
44
+ }
45
+ const config = getBasicAuthConfig();
46
+ if (config.refreshTtlSeconds <= 0 || config.accessTtlSeconds <= 0) {
47
+ throw createInvalidRequestError();
48
+ }
49
+ const sessionId = randomUUID();
50
+ const refreshToken = await signRefreshToken({
51
+ sub: account.id,
52
+ sid: sessionId,
53
+ ver: account.token_version
54
+ });
55
+ createAuthSession({
56
+ accountId: account.id,
57
+ sessionId,
58
+ refreshTokenHash: hashRefreshToken(refreshToken),
59
+ expiresAtMs: Date.now() + config.refreshTtlSeconds * 1e3,
60
+ metadata: getSessionMetadataFromEvent(event)
61
+ });
62
+ const accessToken = await signAccessToken({
63
+ sub: account.id,
64
+ sid: sessionId,
65
+ ver: account.token_version,
66
+ email: account.email,
67
+ display_name: account.display_name
68
+ });
69
+ setAccessCookie(event, accessToken, config.accessTtlSeconds);
70
+ setRefreshCookie(event, refreshToken, config.refreshTtlSeconds);
71
+ return { ok: true };
72
+ }
73
+ export default defineEventHandler(async (event) => {
74
+ return await handleSignIn(event);
75
+ });
@@ -0,0 +1,8 @@
1
+ import { type H3Event } from 'h3';
2
+ export declare function handleSignOut(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,37 @@
1
+ import { defineEventHandler } from "h3";
2
+ import { clearAuthCookies } from "../../lib/cookies.js";
3
+ import {
4
+ getAccessTokenFromEvent,
5
+ getRefreshTokenFromEvent,
6
+ verifyAccessToken,
7
+ verifyRefreshToken
8
+ } from "../../lib/jwt.js";
9
+ import { enforceBasicAuthRateLimit } from "../../lib/rate-limit.js";
10
+ import { revokeSessionById } from "../../lib/session-store.js";
11
+ import { enforceMutationOriginPolicy } from "../../lib/request-security.js";
12
+ import { assertBasicAuthReady, noStore } from "./_helpers.js";
13
+ export async function handleSignOut(event) {
14
+ assertBasicAuthReady(event);
15
+ noStore(event);
16
+ enforceMutationOriginPolicy(event);
17
+ enforceBasicAuthRateLimit(event, "basic-auth:sign-out");
18
+ const refreshToken = getRefreshTokenFromEvent(event);
19
+ if (refreshToken) {
20
+ const refreshClaims = await verifyRefreshToken(refreshToken);
21
+ if (refreshClaims) {
22
+ revokeSessionById(refreshClaims.sid);
23
+ }
24
+ }
25
+ const accessToken = getAccessTokenFromEvent(event);
26
+ if (accessToken) {
27
+ const accessClaims = await verifyAccessToken(accessToken);
28
+ if (accessClaims) {
29
+ revokeSessionById(accessClaims.sid);
30
+ }
31
+ }
32
+ clearAuthCookies(event);
33
+ return { ok: true };
34
+ }
35
+ export default defineEventHandler(async (event) => {
36
+ return await handleSignOut(event);
37
+ });
@@ -0,0 +1,2 @@
1
+ import type { AuthProvider } from '~~/server/auth/types';
2
+ export declare const basicAuthProvider: AuthProvider;
@@ -0,0 +1,41 @@
1
+ import { BASIC_AUTH_PROVIDER_ID } from "../../lib/constants.js";
2
+ import { getAccessTokenFromEvent, verifyAccessToken } from "../lib/jwt.js";
3
+ import {
4
+ findAccountById,
5
+ findSessionById,
6
+ isSessionUsable
7
+ } from "../lib/session-store.js";
8
+ export const basicAuthProvider = {
9
+ name: BASIC_AUTH_PROVIDER_ID,
10
+ async getSession(event) {
11
+ const accessToken = getAccessTokenFromEvent(event);
12
+ if (!accessToken) {
13
+ return null;
14
+ }
15
+ const payload = await verifyAccessToken(accessToken);
16
+ if (!payload) {
17
+ return null;
18
+ }
19
+ const session = findSessionById(payload.sid);
20
+ if (!isSessionUsable(session)) {
21
+ return null;
22
+ }
23
+ const account = findAccountById(payload.sub);
24
+ if (!account) {
25
+ return null;
26
+ }
27
+ if (account.token_version !== payload.ver) {
28
+ return null;
29
+ }
30
+ return {
31
+ provider: BASIC_AUTH_PROVIDER_ID,
32
+ user: {
33
+ id: payload.sub,
34
+ email: payload.email ?? account.email,
35
+ displayName: payload.display_name ?? account.display_name ?? void 0
36
+ },
37
+ expiresAt: new Date((payload.exp ?? 0) * 1e3),
38
+ claims: payload
39
+ };
40
+ }
41
+ };
@@ -0,0 +1 @@
1
+ export { basicAuthProvider } from './basic-auth-provider.js';
@@ -0,0 +1 @@
1
+ export { basicAuthProvider } from "./basic-auth-provider.js";
@@ -0,0 +1,3 @@
1
+ import Database from 'better-sqlite3';
2
+ export declare function getBasicAuthDb(): Database.Database;
3
+ export declare function resetBasicAuthDbForTests(): void;
@@ -0,0 +1,106 @@
1
+ import { dirname } from "node:path";
2
+ import { chmodSync, mkdirSync } from "node:fs";
3
+ import Database from "better-sqlite3";
4
+ import { getBasicAuthConfig } from "../lib/config.js";
5
+ let dbSingleton = null;
6
+ const DB_DIR_MODE = 448;
7
+ const DB_FILE_MODE = 384;
8
+ function hardenDbDirectoryPermissions(dbPath) {
9
+ if (dbPath === ":memory:" || process.platform === "win32") return;
10
+ const dir = dirname(dbPath);
11
+ mkdirSync(dir, { recursive: true, mode: DB_DIR_MODE });
12
+ chmodSync(dir, DB_DIR_MODE);
13
+ }
14
+ function hardenDbFilePermissions(dbPath) {
15
+ if (dbPath === ":memory:" || process.platform === "win32") return;
16
+ chmodSync(dbPath, DB_FILE_MODE);
17
+ }
18
+ function runMigrations(db) {
19
+ const versionRow = db.prepare("PRAGMA user_version").get();
20
+ const version = typeof versionRow?.user_version === "number" ? versionRow.user_version : 0;
21
+ if (version < 1) {
22
+ db.exec(`
23
+ CREATE TABLE IF NOT EXISTS basic_auth_accounts (
24
+ id TEXT PRIMARY KEY,
25
+ email TEXT NOT NULL UNIQUE,
26
+ password_hash TEXT NOT NULL,
27
+ display_name TEXT,
28
+ token_version INTEGER NOT NULL DEFAULT 0,
29
+ created_at INTEGER NOT NULL,
30
+ updated_at INTEGER NOT NULL
31
+ );
32
+
33
+ CREATE TABLE IF NOT EXISTS basic_auth_sessions (
34
+ id TEXT PRIMARY KEY,
35
+ account_id TEXT NOT NULL,
36
+ refresh_token_hash TEXT NOT NULL,
37
+ expires_at INTEGER NOT NULL,
38
+ revoked_at INTEGER,
39
+ created_at INTEGER NOT NULL,
40
+ rotated_from_session_id TEXT,
41
+ replaced_by_session_id TEXT,
42
+ ip_address TEXT,
43
+ user_agent TEXT,
44
+ FOREIGN KEY(account_id) REFERENCES basic_auth_accounts(id)
45
+ );
46
+
47
+ CREATE INDEX IF NOT EXISTS idx_basic_auth_sessions_account_id
48
+ ON basic_auth_sessions(account_id);
49
+ CREATE INDEX IF NOT EXISTS idx_basic_auth_sessions_expires_at
50
+ ON basic_auth_sessions(expires_at);
51
+ CREATE INDEX IF NOT EXISTS idx_basic_auth_sessions_rotated_from
52
+ ON basic_auth_sessions(rotated_from_session_id);
53
+ `);
54
+ db.pragma("user_version = 1");
55
+ }
56
+ if (version < 2) {
57
+ db.exec(`
58
+ CREATE TABLE IF NOT EXISTS basic_auth_rate_limits (
59
+ key TEXT PRIMARY KEY,
60
+ subject TEXT NOT NULL,
61
+ operation TEXT NOT NULL,
62
+ window_started_at INTEGER NOT NULL,
63
+ request_count INTEGER NOT NULL,
64
+ updated_at INTEGER NOT NULL
65
+ );
66
+
67
+ CREATE INDEX IF NOT EXISTS idx_basic_auth_rate_limits_updated_at
68
+ ON basic_auth_rate_limits(updated_at);
69
+ `);
70
+ db.pragma("user_version = 2");
71
+ }
72
+ }
73
+ export function getBasicAuthDb() {
74
+ if (dbSingleton) return dbSingleton;
75
+ const config = getBasicAuthConfig();
76
+ if (config.dbPath !== ":memory:") {
77
+ try {
78
+ hardenDbDirectoryPermissions(config.dbPath);
79
+ } catch (error) {
80
+ throw new Error(
81
+ `[basic-auth] Failed to secure DB directory permissions for "${dirname(config.dbPath)}": ${error.message}`
82
+ );
83
+ }
84
+ }
85
+ const db = new Database(config.dbPath);
86
+ db.pragma("journal_mode = WAL");
87
+ db.pragma("foreign_keys = ON");
88
+ if (config.dbPath !== ":memory:") {
89
+ try {
90
+ hardenDbFilePermissions(config.dbPath);
91
+ } catch (error) {
92
+ db.close();
93
+ throw new Error(
94
+ `[basic-auth] Failed to secure DB file permissions for "${config.dbPath}": ${error.message}`
95
+ );
96
+ }
97
+ }
98
+ runMigrations(db);
99
+ dbSingleton = db;
100
+ return dbSingleton;
101
+ }
102
+ export function resetBasicAuthDbForTests() {
103
+ if (!dbSingleton) return;
104
+ dbSingleton.close();
105
+ dbSingleton = null;
106
+ }
@@ -0,0 +1,21 @@
1
+ export interface BasicAuthAccount {
2
+ id: string;
3
+ email: string;
4
+ password_hash: string;
5
+ display_name: string | null;
6
+ token_version: number;
7
+ created_at: number;
8
+ updated_at: number;
9
+ }
10
+ export interface BasicAuthSession {
11
+ id: string;
12
+ account_id: string;
13
+ refresh_token_hash: string;
14
+ expires_at: number;
15
+ revoked_at: number | null;
16
+ created_at: number;
17
+ rotated_from_session_id: string | null;
18
+ replaced_by_session_id: string | null;
19
+ ip_address: string | null;
20
+ user_agent: string | null;
21
+ }
File without changes
@@ -0,0 +1,22 @@
1
+ export declare const BASIC_AUTH_INSECURE_DEV_ESCAPE_HATCH_ENV = "OR3_BASIC_AUTH_ALLOW_INSECURE_DEV";
2
+ export interface BasicAuthConfig {
3
+ providerId: string;
4
+ enabled: boolean;
5
+ strict: boolean;
6
+ jwtSecret: string;
7
+ refreshSecret: string;
8
+ accessTtlSeconds: number;
9
+ refreshTtlSeconds: number;
10
+ dbPath: string;
11
+ bootstrapEmail?: string;
12
+ bootstrapPassword?: string;
13
+ }
14
+ export interface BasicAuthConfigDiagnostics {
15
+ isValid: boolean;
16
+ errors: string[];
17
+ warnings: string[];
18
+ config: BasicAuthConfig;
19
+ }
20
+ export declare function isBasicAuthInsecureDevEscapeHatchEnabled(): boolean;
21
+ export declare function getBasicAuthConfig(runtimeConfig?: ReturnType<typeof useRuntimeConfig>): BasicAuthConfig;
22
+ export declare function validateBasicAuthConfig(runtimeConfig?: ReturnType<typeof useRuntimeConfig>): BasicAuthConfigDiagnostics;
@@ -0,0 +1,94 @@
1
+ import { resolve } from "node:path";
2
+ import {
3
+ BASIC_AUTH_PROVIDER_ID,
4
+ DEFAULT_ACCESS_TTL_SECONDS,
5
+ DEFAULT_REFRESH_TTL_SECONDS
6
+ } from "../../lib/constants.js";
7
+ export const BASIC_AUTH_INSECURE_DEV_ESCAPE_HATCH_ENV = "OR3_BASIC_AUTH_ALLOW_INSECURE_DEV";
8
+ function parsePositiveInt(input, fallback) {
9
+ if (!input) return fallback;
10
+ const parsed = Number(input);
11
+ if (!Number.isFinite(parsed) || parsed <= 0) {
12
+ return fallback;
13
+ }
14
+ return Math.floor(parsed);
15
+ }
16
+ function isStrictMode(runtimeConfig) {
17
+ if (process.env.OR3_STRICT_CONFIG === "true") return true;
18
+ if (process.env.NODE_ENV === "production") return true;
19
+ return runtimeConfig.auth?.strict === true;
20
+ }
21
+ export function isBasicAuthInsecureDevEscapeHatchEnabled() {
22
+ return process.env.NODE_ENV !== "production" && process.env[BASIC_AUTH_INSECURE_DEV_ESCAPE_HATCH_ENV] === "true";
23
+ }
24
+ export function getBasicAuthConfig(runtimeConfig) {
25
+ const config = runtimeConfig ?? useRuntimeConfig();
26
+ const enabled = config.auth?.enabled === true;
27
+ const providerId = config.auth?.provider || BASIC_AUTH_PROVIDER_ID;
28
+ const cwd = process.cwd();
29
+ const configuredDbPath = process.env.OR3_BASIC_AUTH_DB_PATH;
30
+ const dbPath = configuredDbPath ? configuredDbPath === ":memory:" ? ":memory:" : resolve(cwd, configuredDbPath) : resolve(cwd, ".data/or3-basic-auth.sqlite");
31
+ const jwtSecret = process.env.OR3_BASIC_AUTH_JWT_SECRET ?? "";
32
+ const refreshSecret = process.env.OR3_BASIC_AUTH_REFRESH_SECRET ?? "";
33
+ return {
34
+ providerId,
35
+ enabled,
36
+ strict: isStrictMode(config),
37
+ jwtSecret,
38
+ refreshSecret,
39
+ accessTtlSeconds: parsePositiveInt(
40
+ process.env.OR3_BASIC_AUTH_ACCESS_TTL_SECONDS,
41
+ DEFAULT_ACCESS_TTL_SECONDS
42
+ ),
43
+ refreshTtlSeconds: parsePositiveInt(
44
+ process.env.OR3_BASIC_AUTH_REFRESH_TTL_SECONDS,
45
+ DEFAULT_REFRESH_TTL_SECONDS
46
+ ),
47
+ dbPath,
48
+ bootstrapEmail: process.env.OR3_BASIC_AUTH_BOOTSTRAP_EMAIL,
49
+ bootstrapPassword: process.env.OR3_BASIC_AUTH_BOOTSTRAP_PASSWORD
50
+ };
51
+ }
52
+ export function validateBasicAuthConfig(runtimeConfig) {
53
+ const config = getBasicAuthConfig(runtimeConfig);
54
+ const errors = [];
55
+ const warnings = [];
56
+ if (!config.enabled) {
57
+ warnings.push("auth.enabled=false; basic-auth provider registration skipped.");
58
+ }
59
+ if (config.providerId !== BASIC_AUTH_PROVIDER_ID) {
60
+ warnings.push(`auth.provider=${config.providerId}; basic-auth provider remains idle.`);
61
+ }
62
+ if (!config.jwtSecret) {
63
+ errors.push("Missing OR3_BASIC_AUTH_JWT_SECRET.");
64
+ }
65
+ if (!config.refreshSecret) {
66
+ errors.push("Missing OR3_BASIC_AUTH_REFRESH_SECRET.");
67
+ }
68
+ if (config.accessTtlSeconds > DEFAULT_ACCESS_TTL_SECONDS) {
69
+ warnings.push(
70
+ `OR3_BASIC_AUTH_ACCESS_TTL_SECONDS=${config.accessTtlSeconds} is above recommended ${DEFAULT_ACCESS_TTL_SECONDS}s.`
71
+ );
72
+ }
73
+ if (config.refreshTtlSeconds <= config.accessTtlSeconds) {
74
+ warnings.push(
75
+ "Refresh TTL should be greater than access TTL for practical session refresh behavior."
76
+ );
77
+ }
78
+ if (config.bootstrapEmail && !config.bootstrapPassword || !config.bootstrapEmail && config.bootstrapPassword) {
79
+ warnings.push(
80
+ "Bootstrap account is partially configured. Set both OR3_BASIC_AUTH_BOOTSTRAP_EMAIL and OR3_BASIC_AUTH_BOOTSTRAP_PASSWORD."
81
+ );
82
+ }
83
+ if ((process.env.OR3_BASIC_AUTH_RATE_LIMIT_BACKEND ?? "").trim().toLowerCase() === "memory") {
84
+ warnings.push(
85
+ "OR3_BASIC_AUTH_RATE_LIMIT_BACKEND=memory uses per-process rate limiting and is unsafe for clustered deployments."
86
+ );
87
+ }
88
+ return {
89
+ isValid: errors.length === 0,
90
+ errors,
91
+ warnings,
92
+ config
93
+ };
94
+ }
@@ -0,0 +1,4 @@
1
+ import { type H3Event } from 'h3';
2
+ export declare function setAccessCookie(event: H3Event, token: string, maxAgeSeconds: number): void;
3
+ export declare function setRefreshCookie(event: H3Event, token: string, maxAgeSeconds: number): void;
4
+ export declare function clearAuthCookies(event: H3Event): void;
@@ -0,0 +1,42 @@
1
+ import { setCookie, deleteCookie } from "h3";
2
+ import {
3
+ ACCESS_COOKIE_NAME,
4
+ ACCESS_COOKIE_PATH,
5
+ REFRESH_COOKIE_NAME,
6
+ REFRESH_COOKIE_PATH
7
+ } from "../../lib/constants.js";
8
+ function isProduction() {
9
+ return process.env.NODE_ENV === "production";
10
+ }
11
+ export function setAccessCookie(event, token, maxAgeSeconds) {
12
+ setCookie(event, ACCESS_COOKIE_NAME, token, {
13
+ httpOnly: true,
14
+ sameSite: "lax",
15
+ secure: isProduction(),
16
+ path: ACCESS_COOKIE_PATH,
17
+ maxAge: maxAgeSeconds
18
+ });
19
+ }
20
+ export function setRefreshCookie(event, token, maxAgeSeconds) {
21
+ setCookie(event, REFRESH_COOKIE_NAME, token, {
22
+ httpOnly: true,
23
+ sameSite: "lax",
24
+ secure: isProduction(),
25
+ path: REFRESH_COOKIE_PATH,
26
+ maxAge: maxAgeSeconds
27
+ });
28
+ }
29
+ export function clearAuthCookies(event) {
30
+ deleteCookie(event, ACCESS_COOKIE_NAME, {
31
+ httpOnly: true,
32
+ sameSite: "lax",
33
+ secure: isProduction(),
34
+ path: ACCESS_COOKIE_PATH
35
+ });
36
+ deleteCookie(event, REFRESH_COOKIE_NAME, {
37
+ httpOnly: true,
38
+ sameSite: "lax",
39
+ secure: isProduction(),
40
+ path: REFRESH_COOKIE_PATH
41
+ });
42
+ }
@@ -0,0 +1,4 @@
1
+ export declare function createInvalidCredentialsError(): import("h3").H3Error<unknown>;
2
+ export declare function createSessionExpiredError(): import("h3").H3Error<unknown>;
3
+ export declare function createInvalidRequestError(): import("h3").H3Error<unknown>;
4
+ export declare function createAuthNotConfiguredError(): import("h3").H3Error<unknown>;
@@ -0,0 +1,25 @@
1
+ import { createError } from "h3";
2
+ export function createInvalidCredentialsError() {
3
+ return createError({
4
+ statusCode: 401,
5
+ statusMessage: "Invalid credentials"
6
+ });
7
+ }
8
+ export function createSessionExpiredError() {
9
+ return createError({
10
+ statusCode: 401,
11
+ statusMessage: "Session expired"
12
+ });
13
+ }
14
+ export function createInvalidRequestError() {
15
+ return createError({
16
+ statusCode: 400,
17
+ statusMessage: "Invalid request"
18
+ });
19
+ }
20
+ export function createAuthNotConfiguredError() {
21
+ return createError({
22
+ statusCode: 503,
23
+ statusMessage: "Authentication provider is not configured"
24
+ });
25
+ }
@@ -0,0 +1,37 @@
1
+ import { getCookie } from 'h3';
2
+ import { type JwtPayload } from 'jsonwebtoken';
3
+ interface AccessClaimsInput {
4
+ sub: string;
5
+ sid: string;
6
+ ver: number;
7
+ email?: string;
8
+ display_name?: string | null;
9
+ }
10
+ export interface BasicAccessTokenClaims extends JwtPayload {
11
+ sub: string;
12
+ sid: string;
13
+ ver: number;
14
+ typ: 'access';
15
+ email?: string;
16
+ display_name?: string | null;
17
+ }
18
+ interface RefreshClaimsInput {
19
+ sub: string;
20
+ sid: string;
21
+ ver: number;
22
+ }
23
+ export interface BasicRefreshTokenClaims extends JwtPayload {
24
+ sub: string;
25
+ sid: string;
26
+ ver: number;
27
+ jti: string;
28
+ typ: 'refresh';
29
+ }
30
+ export declare function signAccessToken(input: AccessClaimsInput): Promise<string>;
31
+ export declare function signRefreshToken(input: RefreshClaimsInput): Promise<string>;
32
+ export declare function verifyAccessToken(token: string): Promise<BasicAccessTokenClaims | null>;
33
+ export declare function verifyRefreshToken(token: string): Promise<BasicRefreshTokenClaims | null>;
34
+ export declare function getAccessTokenFromEvent(event: Parameters<typeof getCookie>[0]): string | null;
35
+ export declare function getRefreshTokenFromEvent(event: Parameters<typeof getCookie>[0]): string | null;
36
+ export declare function hashRefreshToken(token: string): string;
37
+ export {};