or3-provider-basic-auth 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 CHANGED
@@ -6,6 +6,7 @@ This package registers:
6
6
 
7
7
  - `AuthProvider` (`basic-auth`)
8
8
  - `ProviderTokenBroker` (`basic-auth`)
9
+ - admin status adapter (`auth-basic-auth`)
9
10
  - provider-owned auth UI adapter + components
10
11
 
11
12
  ## Installation
@@ -35,22 +36,31 @@ export const or3ProviderModules = [
35
36
 
36
37
  Required:
37
38
 
38
- - `AUTH_PROVIDER=basic-auth`
39
+ - `AUTH_PROVIDER=basic-auth` (alias: `OR3_AUTH_PROVIDER`)
40
+ - `SSR_AUTH_ENABLED=true`
39
41
  - `OR3_BASIC_AUTH_JWT_SECRET`
42
+ - `OR3_BASIC_AUTH_REFRESH_SECRET` (use a separate random value; the provider validates it independently)
40
43
 
41
44
  Optional:
42
45
 
43
- - `OR3_BASIC_AUTH_REFRESH_SECRET` (falls back to `OR3_BASIC_AUTH_JWT_SECRET`)
44
46
  - `OR3_BASIC_AUTH_ACCESS_TTL_SECONDS` (default `900`)
45
47
  - `OR3_BASIC_AUTH_REFRESH_TTL_SECONDS` (default `2592000`)
48
+ - `OR3_BASIC_AUTH_ROTATION_GRACE_MS` (default `30000`; concurrent-refresh grace window)
46
49
  - `OR3_BASIC_AUTH_DB_PATH` (default `./.data/or3-basic-auth.sqlite`)
47
50
  - `OR3_BASIC_AUTH_BOOTSTRAP_EMAIL`
48
51
  - `OR3_BASIC_AUTH_BOOTSTRAP_PASSWORD`
52
+ - `OR3_BASIC_AUTH_RATE_LIMIT_BACKEND` (`sqlite` default; `memory` is per-process and intended for single-instance development only)
53
+ - `OR3_BASIC_AUTH_ALLOW_INSECURE_DEV` (`true` in non-production to start with missing secrets instead of failing)
54
+
55
+ Registration mode is a core-auth policy, not a provider setting:
56
+
57
+ - `OR3_AUTH_REGISTRATION_MODE=open|invite_only|disabled`
58
+ - `OR3_AUTH_INVITE_TOKEN_SECRET` and `OR3_AUTH_INVITE_TOKEN_TTL_SECONDS` (required for `invite_only`)
49
59
 
50
60
  Strict-mode behavior:
51
61
 
52
- - `NODE_ENV=production` or `OR3_STRICT_CONFIG=true` fails startup if required secrets are missing.
53
- - non-strict mode logs diagnostics and leaves provider registration disabled.
62
+ - `NODE_ENV=production`, `OR3_STRICT_CONFIG=true`, or runtime config `auth.strict=true` fails startup if required secrets are missing.
63
+ - In non-strict (development) mode, missing secrets also fail startup unless `OR3_BASIC_AUTH_ALLOW_INSECURE_DEV=true` is set — then diagnostics are logged and provider registration is left disabled.
54
64
 
55
65
  ## Architecture notes
56
66
 
@@ -61,16 +71,35 @@ Strict-mode behavior:
61
71
  - Basic Auth account and initial refresh-session rows are created in one local SQLite transaction.
62
72
  - Access JWTs are short-lived and validated by `basicAuthProvider.getSession(event)`.
63
73
  - Refresh tokens are rotated and hashed at rest; replay attempts revoke active sessions.
74
+ - The refresh cookie is scoped to `/api` so `/api/auth/session` can transparently rotate expired access tokens during session resolution.
75
+ - The SQLite DB file and directory are hardened to mode `0600`/`0700`; the DB uses WAL journaling.
76
+
77
+ ## Runtime entrypoints
78
+
79
+ | File | Purpose |
80
+ |---|---|
81
+ | `src/module.ts` | Nuxt module entry — wires `/api/basic-auth/*` handlers, server plugin, and client plugins |
82
+ | `src/runtime/server/plugins/register.ts` | Registers auth provider + token broker + admin adapter into core registries; bootstraps account |
83
+ | `src/runtime/server/auth/basic-auth-provider.ts` | Auth provider — session resolution from access tokens with refresh-token recovery |
84
+ | `src/runtime/server/token-broker/basic-auth-token-broker.ts` | Token broker for direct-mode providers |
85
+ | `src/runtime/server/admin/adapters/auth-basic-auth.ts` | Admin status adapter (config diagnostics) |
86
+ | `src/runtime/server/api/basic-auth/*.post.ts` | Sign-in / register / sign-out / refresh / change-password endpoints |
87
+ | `src/runtime/server/db/client.ts` | SQLite client + migrations (`basic_auth_accounts`, `basic_auth_sessions`, `basic_auth_rate_limits`) |
88
+ | `src/runtime/server/lib/*.ts` | Config, JWT, cookies, password, rate-limit, session store, refresh rotation |
89
+ | `src/runtime/plugins/basic-auth-ui.client.ts` | Registers auth UI adapter + lock-page adapter |
90
+ | `src/runtime/plugins/auth-status.client.ts` | Client auth-status resolver + silent-refresh recovery |
91
+ | `src/runtime/components/*.client.vue` | Provider-owned auth UI (modals, user menu, sidebar button, lock page) |
64
92
 
65
93
  ## Troubleshooting
66
94
 
67
95
  - `Authentication provider is not configured`
68
96
  - Ensure `AUTH_PROVIDER=basic-auth` and `SSR_AUTH_ENABLED=true`.
69
- - Ensure `OR3_BASIC_AUTH_JWT_SECRET` is set.
97
+ - Ensure `OR3_BASIC_AUTH_JWT_SECRET` and `OR3_BASIC_AUTH_REFRESH_SECRET` are set.
70
98
 
71
- - `Missing OR3_BASIC_AUTH_JWT_SECRET`
99
+ - `Missing OR3_BASIC_AUTH_JWT_SECRET` / `Missing OR3_BASIC_AUTH_REFRESH_SECRET`
72
100
  - In strict mode (`NODE_ENV=production` or `OR3_STRICT_CONFIG=true`), startup fails intentionally.
73
- - Add the missing secret and restart.
101
+ - In development, set `OR3_BASIC_AUTH_ALLOW_INSECURE_DEV=true` to start with registration disabled instead.
102
+ - Add the missing secret(s) and restart.
74
103
 
75
104
  - `Invalid credentials` on known user
76
105
  - Verify the bootstrap account values.
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "or3-provider-basic-auth",
3
3
  "configKey": "or3-provider-basic-auth",
4
- "version": "0.0.6",
4
+ "version": "0.0.8",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -2,8 +2,8 @@
2
2
  <UPopover
3
3
  v-model:open="isOpen"
4
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' }"
5
+ :content="popoverContent"
6
+ :ui="{ content: 'z-[280] max-w-[240px] p-0 border-none bg-transparent shadow-none' }"
7
7
  >
8
8
  <UButton
9
9
  v-bind="triggerButtonProps"
@@ -12,8 +12,8 @@
12
12
  >
13
13
  <template #default>
14
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">
15
+ <UIcon name="lucide:user" class="h-[18px] w-[18px]" />
16
+ <span class="sidebar-rail-caption text-[7px] uppercase tracking-wider whitespace-nowrap">
17
17
  Account
18
18
  </span>
19
19
  </span>
@@ -22,13 +22,13 @@
22
22
 
23
23
  <template #content>
24
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"
25
+ class="basic-auth-account-menu 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
26
  >
27
27
  <!-- User identity section -->
28
28
  <div class="px-4 pt-4 pb-3">
29
29
  <div class="flex items-center gap-3">
30
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" />
31
+ <UIcon name="lucide:user" class="w-4 h-4" />
32
32
  </div>
33
33
  <div class="min-w-0">
34
34
  <p class="text-[10px] uppercase tracking-wider text-[var(--md-on-surface)]/50 mb-0.5">
@@ -50,7 +50,7 @@
50
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
51
  @click="onChangePassword"
52
52
  >
53
- <UIcon name="i-lucide-key-round" class="w-4 h-4 text-[var(--md-on-surface)]/60 shrink-0" />
53
+ <UIcon name="lucide:key-round" class="w-4 h-4 text-[var(--md-on-surface)]/60 shrink-0" />
54
54
  Change Password
55
55
  </button>
56
56
 
@@ -60,7 +60,7 @@
60
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
61
  @click="onSignOut"
62
62
  >
63
- <UIcon name="i-lucide-log-out" class="w-4 h-4 shrink-0" />
63
+ <UIcon name="lucide:log-out" class="w-4 h-4 shrink-0" />
64
64
  {{ pending ? "Signing out\u2026" : "Sign Out" }}
65
65
  </button>
66
66
  </div>
@@ -70,7 +70,10 @@
70
70
  </template>
71
71
 
72
72
  <script setup>
73
- import { ref } from "vue";
73
+ import { computed, inject, ref, unref } from "vue";
74
+ import {
75
+ AUTH_UI_POPOVER_CONTENT_KEY
76
+ } from "../lib/auth-ui-popover";
74
77
  defineProps({
75
78
  email: { type: String, required: false },
76
79
  displayName: { type: String, required: false }
@@ -78,6 +81,20 @@ defineProps({
78
81
  const emit = defineEmits(["signed-out", "change-password"]);
79
82
  const isOpen = ref(false);
80
83
  const pending = ref(false);
84
+ const DEFAULT_POPOVER_CONTENT = {
85
+ side: "right",
86
+ align: "end",
87
+ sideOffset: 10,
88
+ collisionPadding: 12
89
+ };
90
+ const injectedPopoverContent = inject(
91
+ AUTH_UI_POPOVER_CONTENT_KEY,
92
+ null
93
+ );
94
+ const popoverContent = computed(() => ({
95
+ ...DEFAULT_POPOVER_CONTENT,
96
+ ...injectedPopoverContent ? unref(injectedPopoverContent) : null
97
+ }));
81
98
  const triggerButtonProps = {
82
99
  block: true,
83
100
  variant: "ghost",
@@ -17,8 +17,8 @@
17
17
  >
18
18
  <template #default>
19
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">
20
+ <UIcon name="lucide:log-in" class="h-[18px] w-[18px]" />
21
+ <span class="sidebar-rail-caption text-[7px] uppercase tracking-wider whitespace-nowrap">
22
22
  Login
23
23
  </span>
24
24
  </span>
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Floating UI content options for auth UI popovers.
3
+ * Hosts (e.g. mobile More menu) can inject overrides so nested menus
4
+ * stay on-screen instead of defaulting to rail `side: 'right'`.
5
+ */
6
+ export interface AuthUiPopoverContent {
7
+ side?: 'top' | 'right' | 'bottom' | 'left';
8
+ align?: 'start' | 'center' | 'end';
9
+ sideOffset?: number;
10
+ alignOffset?: number;
11
+ collisionPadding?: number | Partial<Record<'top' | 'right' | 'bottom' | 'left', number>>;
12
+ }
13
+ /** String key so host app + provider package can share without a linked import. */
14
+ export declare const AUTH_UI_POPOVER_CONTENT_KEY = "or3:auth-ui-popover-content";
@@ -0,0 +1 @@
1
+ export const AUTH_UI_POPOVER_CONTENT_KEY = "or3:auth-ui-popover-content";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "or3-provider-basic-auth",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Saluana/or3-provider-basic-auth.git"
@@ -40,6 +40,7 @@
40
40
  "build": "nuxt-module-build build",
41
41
  "lint": "eslint \"src/**/*.{ts,d.ts,vue}\"",
42
42
  "type-check": "tsc -p tsconfig.typecheck.json --noEmit",
43
+ "type-check:standalone": "tsc -p tsconfig.typecheck.json --noEmit",
43
44
  "test": "vitest run"
44
45
  },
45
46
  "publishConfig": {
@@ -47,7 +48,7 @@
47
48
  },
48
49
  "dependencies": {
49
50
  "bcryptjs": "^3.0.3",
50
- "better-sqlite3": "^12.4.1",
51
+ "better-sqlite3": "^13.0.3",
51
52
  "jsonwebtoken": "^9.0.2",
52
53
  "zod": "^4.1.12"
53
54
  },