or3-provider-basic-auth 0.0.10 → 0.0.11

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
@@ -69,6 +69,7 @@ Strict-mode behavior:
69
69
  - Canonical OR3 users/workspaces are still resolved by the selected `AuthWorkspaceStore`.
70
70
  - In `invite_only` mode, signed-token, expiry, persisted invite state/token hash, and normalized-email validation runs before Basic Auth creates an account or session.
71
71
  - The selected `AuthWorkspaceStore` must support atomic invite provisioning; internal user/auth mapping, membership, and invite consumption are committed together by that provider.
72
+ - Invite links (`/?invite=TOKEN`, copied from Admin → Workspaces, or carried in the lock page's `next`) open registration for signed-out visitors with the token filled in. The token is removed from the address bar on first read and kept in memory for that page; only one mounted auth control (sidebar, mobile More sheet or lock page) opens registration per link. On mobile the account control lives in More, so registration opens when the visitor opens More. This invite-link handoff requires published `or3-provider-basic-auth@0.0.11` or later.
72
73
  - Basic Auth account and initial refresh-session rows are created in one local SQLite transaction.
73
74
  - Access JWTs are short-lived and validated by `basicAuthProvider.getSession(event)`.
74
75
  - Refresh tokens are rotated and hashed at rest; replay attempts revoke active sessions.
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.10",
4
+ "version": "0.0.11",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -23,18 +23,25 @@
23
23
 
24
24
  <BasicAuthRegisterModal
25
25
  v-model="registerModalOpen"
26
+ :invite-token="inviteToken"
26
27
  @registered="handleRegistered"
27
28
  />
28
29
  </div>
29
30
  </template>
30
31
 
31
32
  <script setup>
32
- import { nextTick, ref } from "vue";
33
+ import { nextTick, onMounted, ref } from "vue";
33
34
  import BasicAuthRegisterModal from "./BasicAuthRegisterModal.client.vue";
34
35
  import BasicAuthSignInModal from "./BasicAuthSignInModal.client.vue";
36
+ import { claimInviteLinkAutoOpen, readInviteLinkToken } from "../lib/invite-link.client";
35
37
  const loginIcon = "i-lucide-log-in";
36
38
  const signInModalOpen = ref(false);
37
39
  const registerModalOpen = ref(false);
40
+ const inviteToken = ref(null);
41
+ onMounted(() => {
42
+ inviteToken.value = readInviteLinkToken();
43
+ if (claimInviteLinkAutoOpen()) registerModalOpen.value = true;
44
+ });
38
45
  function notifyAuthSessionChanged() {
39
46
  if (typeof window === "undefined") return;
40
47
  window.dispatchEvent(new CustomEvent("or3:auth-session-changed"));
@@ -1,5 +1,7 @@
1
1
  type __VLS_Props = {
2
2
  modelValue: boolean;
3
+ /** Token from an invite link; filled in when the form opens. */
4
+ inviteToken?: string | null;
3
5
  };
4
6
  declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
5
7
  "update:modelValue": (value: boolean) => any;
@@ -73,7 +73,10 @@
73
73
  />
74
74
  </UFormField>
75
75
 
76
- <UFormField label="Invite token (optional)" name="inviteToken">
76
+ <UFormField
77
+ :label="props.inviteToken ? 'Invite token (from your invite link)' : 'Invite token (optional)'"
78
+ name="inviteToken"
79
+ >
77
80
  <UInput
78
81
  v-model="state.inviteToken"
79
82
  type="text"
@@ -111,14 +114,15 @@
111
114
  </template>
112
115
 
113
116
  <script setup>
114
- import { computed, reactive, ref } from "vue";
117
+ import { computed, reactive, ref, watch } from "vue";
115
118
  import { newPasswordFitsBcrypt } from "../password-policy";
116
119
  const modalUi = {
117
120
  overlay: "basic-auth-modal-overlay",
118
121
  content: "basic-auth-modal-content sm:max-w-[420px]"
119
122
  };
120
123
  const props = defineProps({
121
- modelValue: { type: Boolean, required: true }
124
+ modelValue: { type: Boolean, required: true },
125
+ inviteToken: { type: [String, null], required: false }
122
126
  });
123
127
  const emit = defineEmits(["update:modelValue", "registered"]);
124
128
  const state = reactive({
@@ -136,6 +140,9 @@ const isOpen = computed({
136
140
  emit("update:modelValue", value);
137
141
  }
138
142
  });
143
+ watch(() => [props.modelValue, props.inviteToken], ([open, token]) => {
144
+ if (open && token && !state.inviteToken.trim()) state.inviteToken = token;
145
+ }, { immediate: true });
139
146
  function close() {
140
147
  errorMessage.value = "";
141
148
  emit("update:modelValue", false);
@@ -1,5 +1,7 @@
1
1
  type __VLS_Props = {
2
2
  modelValue: boolean;
3
+ /** Token from an invite link; filled in when the form opens. */
4
+ inviteToken?: string | null;
3
5
  };
4
6
  declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
5
7
  "update:modelValue": (value: boolean) => any;
@@ -57,6 +57,7 @@
57
57
 
58
58
  <BasicAuthRegisterModal
59
59
  v-model="registerModalOpen"
60
+ :invite-token="inviteToken"
60
61
  @registered="onRegistered"
61
62
  />
62
63
 
@@ -76,6 +77,7 @@ import {
76
77
  AUTH_UI_LAYOUT_KEY
77
78
  } from "../lib/auth-ui-layout";
78
79
  import { silentRefreshOnce } from "../lib/silent-refresh.client";
80
+ import { claimInviteLinkAutoOpen, readInviteLinkToken } from "../lib/invite-link.client";
79
81
  import BasicAuthSignInModal from "./BasicAuthSignInModal.client.vue";
80
82
  import BasicAuthRegisterModal from "./BasicAuthRegisterModal.client.vue";
81
83
  import BasicAuthUserMenu from "./BasicAuthUserMenu.client.vue";
@@ -171,8 +173,13 @@ async function onRegistered() {
171
173
  await refreshSession({ allowSilentRefresh: false });
172
174
  notifyAuthSessionChanged();
173
175
  }
174
- onMounted(() => {
175
- void refreshSession();
176
+ const inviteToken = ref(null);
177
+ onMounted(async () => {
178
+ inviteToken.value = readInviteLinkToken();
179
+ await refreshSession();
180
+ if (!isSignedIn.value && isBasicAuthProvider.value && claimInviteLinkAutoOpen()) {
181
+ registerModalOpen.value = true;
182
+ }
176
183
  });
177
184
  </script>
178
185
 
@@ -0,0 +1,4 @@
1
+ export declare function readInviteLinkToken(): string | null;
2
+ /** True for the one caller that should open registration for this link. */
3
+ export declare function claimInviteLinkAutoOpen(): boolean;
4
+ export declare function resetInviteLinkForTests(): void;
@@ -0,0 +1,39 @@
1
+ let token;
2
+ let autoOpenClaimed = false;
3
+ function takeInvite(url) {
4
+ const direct = url.searchParams.get("invite")?.trim();
5
+ if (direct) {
6
+ url.searchParams.delete("invite");
7
+ return direct;
8
+ }
9
+ const next = url.searchParams.get("next");
10
+ if (!next) return null;
11
+ const target = new URL(next, url.origin);
12
+ const carried = target.searchParams.get("invite")?.trim();
13
+ if (!carried) return null;
14
+ target.searchParams.delete("invite");
15
+ url.searchParams.set("next", target.pathname + target.search + target.hash);
16
+ return carried;
17
+ }
18
+ export function readInviteLinkToken() {
19
+ if (token !== void 0) return token;
20
+ token = null;
21
+ if (typeof window === "undefined") return token;
22
+ try {
23
+ const url = new URL(window.location.href);
24
+ token = takeInvite(url);
25
+ if (token) window.history.replaceState(window.history.state, "", url.pathname + url.search + url.hash);
26
+ } catch {
27
+ token = null;
28
+ }
29
+ return token;
30
+ }
31
+ export function claimInviteLinkAutoOpen() {
32
+ if (!readInviteLinkToken() || autoOpenClaimed) return false;
33
+ autoOpenClaimed = true;
34
+ return true;
35
+ }
36
+ export function resetInviteLinkForTests() {
37
+ token = void 0;
38
+ autoOpenClaimed = false;
39
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "or3-provider-basic-auth",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/Saluana/or3-provider-basic-auth.git"