@bagelink/auth 1.7.92 → 1.7.96

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/dist/api.d.ts CHANGED
@@ -1,7 +1,16 @@
1
- import { RegisterRequest, UpdateAccountRequest, ChangePasswordRequest, ResetPasswordRequest, SendVerificationRequest, AuthenticationAccount, LoginResponse, RegisterResponse, LogoutResponse, GetMeResponse, UpdateMeResponse, DeleteMeResponse, GetAccountResponse, UpdateAccountResponse, DeleteAccountResponse, ActivateAccountResponse, DeactivateAccountResponse, ChangePasswordResponse, ForgotPasswordResponse, ResetPasswordResponse, VerifyResetTokenResponse, SendVerificationResponse, VerifyEmailResponse, RefreshSessionResponse, GetSessionsResponse, DeleteSessionResponse, DeleteAllSessionsResponse, CleanupSessionsResponse, GetMethodsResponse, SSOProvider, SSOInitiateRequest, SSOCallbackRequest, SSOLinkRequest, SSOInitiateResponse, SSOCallbackResponse, SSOLinkResponse, SSOUnlinkResponse } from './types';
1
+ import { RegisterRequest, UpdateAccountRequest, ChangePasswordRequest, ResetPasswordRequest, SendVerificationRequest, AuthenticationAccount, LoginResponse, RegisterResponse, LogoutResponse, GetMeResponse, UpdateMeResponse, DeleteMeResponse, GetAccountResponse, UpdateAccountResponse, DeleteAccountResponse, ActivateAccountResponse, DeactivateAccountResponse, ChangePasswordResponse, ForgotPasswordResponse, ResetPasswordResponse, VerifyResetTokenResponse, SendVerificationResponse, VerifyEmailResponse, RefreshSessionResponse, GetSessionsResponse, DeleteSessionResponse, DeleteAllSessionsResponse, CleanupSessionsResponse, GetMethodsResponse, SSOProvider, SSOInitiateRequest, SSOCallbackRequest, SSOLinkRequest, SSOInitiateResponse, SSOCallbackResponse, SSOLinkResponse, SSOUnlinkResponse, GetTenantsResponse } from './types';
2
2
  export declare class AuthApi {
3
3
  private api;
4
+ private currentTenantId;
4
5
  constructor(baseURL?: string);
6
+ /**
7
+ * Set the current tenant ID for multi-tenant requests
8
+ */
9
+ setTenantId(tenantId: string | null): void;
10
+ /**
11
+ * Get the current tenant ID
12
+ */
13
+ getTenantId(): string | null;
5
14
  private setupInterceptors;
6
15
  /**
7
16
  * Get available authentication methods
@@ -112,4 +121,8 @@ export declare class AuthApi {
112
121
  * Cleanup expired sessions (admin)
113
122
  */
114
123
  cleanupSessions(): Promise<CleanupSessionsResponse>;
124
+ /**
125
+ * Get list of tenants the authenticated user belongs to
126
+ */
127
+ getTenants(): Promise<GetTenantsResponse>;
115
128
  }
package/dist/index.cjs CHANGED
@@ -65,9 +65,22 @@ function queryParams() {
65
65
  class AuthApi {
66
66
  constructor(baseURL = "") {
67
67
  __publicField(this, "api");
68
+ __publicField(this, "currentTenantId", null);
68
69
  this.api = createAxiosInstance(baseURL);
69
70
  this.setupInterceptors();
70
71
  }
72
+ /**
73
+ * Set the current tenant ID for multi-tenant requests
74
+ */
75
+ setTenantId(tenantId) {
76
+ this.currentTenantId = tenantId;
77
+ }
78
+ /**
79
+ * Get the current tenant ID
80
+ */
81
+ getTenantId() {
82
+ return this.currentTenantId;
83
+ }
71
84
  setupInterceptors() {
72
85
  this.api.interceptors.request.use((config) => {
73
86
  const urlParams = new URLSearchParams(window.location.search);
@@ -75,6 +88,9 @@ class AuthApi {
75
88
  if (resetToken !== null) {
76
89
  config.headers["X-Reset-Token"] = resetToken;
77
90
  }
91
+ if (this.currentTenantId !== null) {
92
+ config.headers["X-Tenant-ID"] = this.currentTenantId;
93
+ }
78
94
  return config;
79
95
  });
80
96
  }
@@ -281,6 +297,15 @@ class AuthApi {
281
297
  async cleanupSessions() {
282
298
  return this.api.post("/authentication/cleanup-sessions", {});
283
299
  }
300
+ // ============================================
301
+ // Multi-Tenancy Methods
302
+ // ============================================
303
+ /**
304
+ * Get list of tenants the authenticated user belongs to
305
+ */
306
+ async getTenants() {
307
+ return this.api.get("/tenants");
308
+ }
284
309
  }
285
310
  const _hoisted_1$8 = { class: "txt20 bold mb-1" };
286
311
  const _hoisted_2$4 = { class: "txt-center opacity-7" };
@@ -1902,35 +1927,24 @@ function accountToUser(account) {
1902
1927
  if (account === null) {
1903
1928
  return null;
1904
1929
  }
1905
- if (account.person !== void 0) {
1930
+ const hasPersonLinked = account.person !== void 0 && account.person !== null;
1931
+ if (hasPersonLinked) {
1906
1932
  return {
1907
1933
  id: account.person.id,
1908
1934
  accountId: account.id,
1909
1935
  name: account.person.name,
1910
- email: account.person.email,
1936
+ email: account.person.email ?? void 0,
1911
1937
  type: account.account_type,
1912
1938
  roles: account.person.roles,
1913
1939
  isActive: account.is_active,
1914
1940
  isVerified: account.is_verified,
1915
1941
  person: account.person,
1916
- lastLogin: account.last_login
1917
- };
1918
- }
1919
- if (account.entity !== void 0) {
1920
- return {
1921
- id: account.entity.id,
1922
- accountId: account.id,
1923
- name: account.entity.name,
1924
- type: account.account_type,
1925
- isActive: account.is_active,
1926
- isVerified: account.is_verified,
1927
- lastLogin: account.last_login,
1928
- entityType: account.entity.type,
1929
- metadata: account.entity.metadata
1942
+ lastLogin: account.last_login ?? void 0,
1943
+ hasPersonLinked: true
1930
1944
  };
1931
1945
  }
1932
1946
  const emailMethod = account.authentication_methods.find(
1933
- (m) => m.type === "password" || m.type === "email_token"
1947
+ (m) => m.type === "password" || m.type === "email_token" || m.type === "sso"
1934
1948
  );
1935
1949
  return {
1936
1950
  id: account.id,
@@ -1940,7 +1954,8 @@ function accountToUser(account) {
1940
1954
  type: account.account_type,
1941
1955
  isActive: account.is_active,
1942
1956
  isVerified: account.is_verified,
1943
- lastLogin: account.last_login
1957
+ lastLogin: account.last_login ?? void 0,
1958
+ hasPersonLinked: false
1944
1959
  };
1945
1960
  }
1946
1961
  const DEFAULT_REDIRECT_CONFIG = {
@@ -1965,6 +1980,8 @@ let redirectConfig = null;
1965
1980
  let autoRedirectRouter = null;
1966
1981
  let cachedAuthGuard = null;
1967
1982
  const accountInfo = vue.ref(null);
1983
+ const tenants = vue.ref([]);
1984
+ const currentTenant = vue.ref(null);
1968
1985
  function getRedirectConfig() {
1969
1986
  if (!redirectConfig) {
1970
1987
  throw new Error("Redirect config not initialized. Did you call createAuth with redirect config?");
@@ -2135,15 +2152,11 @@ function useAuth() {
2135
2152
  };
2136
2153
  const getAccountType = () => {
2137
2154
  var _a;
2138
- return ((_a = user.value) == null ? void 0 : _a.type) ?? "person";
2155
+ return ((_a = user.value) == null ? void 0 : _a.type) ?? "identity";
2139
2156
  };
2140
2157
  const isPersonAccount = () => {
2141
2158
  var _a;
2142
- return ((_a = user.value) == null ? void 0 : _a.type) === "person";
2143
- };
2144
- const isEntityAccount = () => {
2145
- var _a;
2146
- return ((_a = user.value) == null ? void 0 : _a.type) === "entity";
2159
+ return ((_a = user.value) == null ? void 0 : _a.hasPersonLinked) === true;
2147
2160
  };
2148
2161
  async function logout() {
2149
2162
  const logoutPromise = api.logout();
@@ -2176,6 +2189,45 @@ function useAuth() {
2176
2189
  return false;
2177
2190
  }
2178
2191
  }
2192
+ async function loadTenants() {
2193
+ try {
2194
+ const { data } = await api.getTenants();
2195
+ tenants.value = data;
2196
+ if (currentTenant.value === null && tenants.value.length > 0) {
2197
+ const firstActiveTenant = tenants.value.find((t) => t.status === "active");
2198
+ if (firstActiveTenant !== void 0) {
2199
+ setTenant(firstActiveTenant.id);
2200
+ }
2201
+ }
2202
+ return tenants.value;
2203
+ } catch {
2204
+ tenants.value = [];
2205
+ return [];
2206
+ }
2207
+ }
2208
+ function setTenant(tenantId) {
2209
+ if (tenantId === null) {
2210
+ currentTenant.value = null;
2211
+ api.setTenantId(null);
2212
+ return;
2213
+ }
2214
+ const tenant = tenants.value.find((t) => t.id === tenantId);
2215
+ if (tenant !== void 0) {
2216
+ currentTenant.value = tenant;
2217
+ api.setTenantId(tenantId);
2218
+ } else {
2219
+ throw new Error(`Tenant with ID ${tenantId} not found`);
2220
+ }
2221
+ }
2222
+ function switchTenant(tenantId) {
2223
+ setTenant(tenantId);
2224
+ }
2225
+ function getTenants() {
2226
+ return tenants.value;
2227
+ }
2228
+ function getCurrentTenant() {
2229
+ return currentTenant.value;
2230
+ }
2179
2231
  async function signup(newUser) {
2180
2232
  const hasPassword = newUser.password !== void 0 && newUser.password.length > 0;
2181
2233
  if (hasPassword && newUser.password !== newUser.confirmPassword) {
@@ -2290,6 +2342,9 @@ function useAuth() {
2290
2342
  accountInfo,
2291
2343
  // SSO Providers (ready to use!)
2292
2344
  sso,
2345
+ // Multi-Tenancy State
2346
+ tenants,
2347
+ currentTenant,
2293
2348
  // Getters
2294
2349
  getFullName,
2295
2350
  getIsLoggedIn,
@@ -2297,7 +2352,8 @@ function useAuth() {
2297
2352
  getRoles,
2298
2353
  getAccountType,
2299
2354
  isPersonAccount,
2300
- isEntityAccount,
2355
+ getTenants,
2356
+ getCurrentTenant,
2301
2357
  // Authentication Actions
2302
2358
  login,
2303
2359
  logout,
@@ -2327,7 +2383,11 @@ function useAuth() {
2327
2383
  // Session Management
2328
2384
  getSessions,
2329
2385
  revokeSession,
2330
- revokeAllSessions
2386
+ revokeAllSessions,
2387
+ // Multi-Tenancy Actions
2388
+ loadTenants,
2389
+ setTenant,
2390
+ switchTenant
2331
2391
  };
2332
2392
  }
2333
2393
  const useAuth$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
package/dist/index.mjs CHANGED
@@ -63,9 +63,22 @@ function queryParams() {
63
63
  class AuthApi {
64
64
  constructor(baseURL = "") {
65
65
  __publicField(this, "api");
66
+ __publicField(this, "currentTenantId", null);
66
67
  this.api = createAxiosInstance(baseURL);
67
68
  this.setupInterceptors();
68
69
  }
70
+ /**
71
+ * Set the current tenant ID for multi-tenant requests
72
+ */
73
+ setTenantId(tenantId) {
74
+ this.currentTenantId = tenantId;
75
+ }
76
+ /**
77
+ * Get the current tenant ID
78
+ */
79
+ getTenantId() {
80
+ return this.currentTenantId;
81
+ }
69
82
  setupInterceptors() {
70
83
  this.api.interceptors.request.use((config) => {
71
84
  const urlParams = new URLSearchParams(window.location.search);
@@ -73,6 +86,9 @@ class AuthApi {
73
86
  if (resetToken !== null) {
74
87
  config.headers["X-Reset-Token"] = resetToken;
75
88
  }
89
+ if (this.currentTenantId !== null) {
90
+ config.headers["X-Tenant-ID"] = this.currentTenantId;
91
+ }
76
92
  return config;
77
93
  });
78
94
  }
@@ -279,6 +295,15 @@ class AuthApi {
279
295
  async cleanupSessions() {
280
296
  return this.api.post("/authentication/cleanup-sessions", {});
281
297
  }
298
+ // ============================================
299
+ // Multi-Tenancy Methods
300
+ // ============================================
301
+ /**
302
+ * Get list of tenants the authenticated user belongs to
303
+ */
304
+ async getTenants() {
305
+ return this.api.get("/tenants");
306
+ }
282
307
  }
283
308
  const _hoisted_1$8 = { class: "txt20 bold mb-1" };
284
309
  const _hoisted_2$4 = { class: "txt-center opacity-7" };
@@ -1900,35 +1925,24 @@ function accountToUser(account) {
1900
1925
  if (account === null) {
1901
1926
  return null;
1902
1927
  }
1903
- if (account.person !== void 0) {
1928
+ const hasPersonLinked = account.person !== void 0 && account.person !== null;
1929
+ if (hasPersonLinked) {
1904
1930
  return {
1905
1931
  id: account.person.id,
1906
1932
  accountId: account.id,
1907
1933
  name: account.person.name,
1908
- email: account.person.email,
1934
+ email: account.person.email ?? void 0,
1909
1935
  type: account.account_type,
1910
1936
  roles: account.person.roles,
1911
1937
  isActive: account.is_active,
1912
1938
  isVerified: account.is_verified,
1913
1939
  person: account.person,
1914
- lastLogin: account.last_login
1915
- };
1916
- }
1917
- if (account.entity !== void 0) {
1918
- return {
1919
- id: account.entity.id,
1920
- accountId: account.id,
1921
- name: account.entity.name,
1922
- type: account.account_type,
1923
- isActive: account.is_active,
1924
- isVerified: account.is_verified,
1925
- lastLogin: account.last_login,
1926
- entityType: account.entity.type,
1927
- metadata: account.entity.metadata
1940
+ lastLogin: account.last_login ?? void 0,
1941
+ hasPersonLinked: true
1928
1942
  };
1929
1943
  }
1930
1944
  const emailMethod = account.authentication_methods.find(
1931
- (m) => m.type === "password" || m.type === "email_token"
1945
+ (m) => m.type === "password" || m.type === "email_token" || m.type === "sso"
1932
1946
  );
1933
1947
  return {
1934
1948
  id: account.id,
@@ -1938,7 +1952,8 @@ function accountToUser(account) {
1938
1952
  type: account.account_type,
1939
1953
  isActive: account.is_active,
1940
1954
  isVerified: account.is_verified,
1941
- lastLogin: account.last_login
1955
+ lastLogin: account.last_login ?? void 0,
1956
+ hasPersonLinked: false
1942
1957
  };
1943
1958
  }
1944
1959
  const DEFAULT_REDIRECT_CONFIG = {
@@ -1963,6 +1978,8 @@ let redirectConfig = null;
1963
1978
  let autoRedirectRouter = null;
1964
1979
  let cachedAuthGuard = null;
1965
1980
  const accountInfo = ref(null);
1981
+ const tenants = ref([]);
1982
+ const currentTenant = ref(null);
1966
1983
  function getRedirectConfig() {
1967
1984
  if (!redirectConfig) {
1968
1985
  throw new Error("Redirect config not initialized. Did you call createAuth with redirect config?");
@@ -2133,15 +2150,11 @@ function useAuth() {
2133
2150
  };
2134
2151
  const getAccountType = () => {
2135
2152
  var _a;
2136
- return ((_a = user.value) == null ? void 0 : _a.type) ?? "person";
2153
+ return ((_a = user.value) == null ? void 0 : _a.type) ?? "identity";
2137
2154
  };
2138
2155
  const isPersonAccount = () => {
2139
2156
  var _a;
2140
- return ((_a = user.value) == null ? void 0 : _a.type) === "person";
2141
- };
2142
- const isEntityAccount = () => {
2143
- var _a;
2144
- return ((_a = user.value) == null ? void 0 : _a.type) === "entity";
2157
+ return ((_a = user.value) == null ? void 0 : _a.hasPersonLinked) === true;
2145
2158
  };
2146
2159
  async function logout() {
2147
2160
  const logoutPromise = api.logout();
@@ -2174,6 +2187,45 @@ function useAuth() {
2174
2187
  return false;
2175
2188
  }
2176
2189
  }
2190
+ async function loadTenants() {
2191
+ try {
2192
+ const { data } = await api.getTenants();
2193
+ tenants.value = data;
2194
+ if (currentTenant.value === null && tenants.value.length > 0) {
2195
+ const firstActiveTenant = tenants.value.find((t) => t.status === "active");
2196
+ if (firstActiveTenant !== void 0) {
2197
+ setTenant(firstActiveTenant.id);
2198
+ }
2199
+ }
2200
+ return tenants.value;
2201
+ } catch {
2202
+ tenants.value = [];
2203
+ return [];
2204
+ }
2205
+ }
2206
+ function setTenant(tenantId) {
2207
+ if (tenantId === null) {
2208
+ currentTenant.value = null;
2209
+ api.setTenantId(null);
2210
+ return;
2211
+ }
2212
+ const tenant = tenants.value.find((t) => t.id === tenantId);
2213
+ if (tenant !== void 0) {
2214
+ currentTenant.value = tenant;
2215
+ api.setTenantId(tenantId);
2216
+ } else {
2217
+ throw new Error(`Tenant with ID ${tenantId} not found`);
2218
+ }
2219
+ }
2220
+ function switchTenant(tenantId) {
2221
+ setTenant(tenantId);
2222
+ }
2223
+ function getTenants() {
2224
+ return tenants.value;
2225
+ }
2226
+ function getCurrentTenant() {
2227
+ return currentTenant.value;
2228
+ }
2177
2229
  async function signup(newUser) {
2178
2230
  const hasPassword = newUser.password !== void 0 && newUser.password.length > 0;
2179
2231
  if (hasPassword && newUser.password !== newUser.confirmPassword) {
@@ -2288,6 +2340,9 @@ function useAuth() {
2288
2340
  accountInfo,
2289
2341
  // SSO Providers (ready to use!)
2290
2342
  sso,
2343
+ // Multi-Tenancy State
2344
+ tenants,
2345
+ currentTenant,
2291
2346
  // Getters
2292
2347
  getFullName,
2293
2348
  getIsLoggedIn,
@@ -2295,7 +2350,8 @@ function useAuth() {
2295
2350
  getRoles,
2296
2351
  getAccountType,
2297
2352
  isPersonAccount,
2298
- isEntityAccount,
2353
+ getTenants,
2354
+ getCurrentTenant,
2299
2355
  // Authentication Actions
2300
2356
  login,
2301
2357
  logout,
@@ -2325,7 +2381,11 @@ function useAuth() {
2325
2381
  // Session Management
2326
2382
  getSessions,
2327
2383
  revokeSession,
2328
- revokeAllSessions
2384
+ revokeAllSessions,
2385
+ // Multi-Tenancy Actions
2386
+ loadTenants,
2387
+ setTenant,
2388
+ switchTenant
2329
2389
  };
2330
2390
  }
2331
2391
  const useAuth$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
package/dist/types.d.ts CHANGED
@@ -22,7 +22,20 @@ export interface AuthEventMap {
22
22
  [AuthState.EMAIL_VERIFIED]: AuthEventHandler;
23
23
  [AuthState.SESSION_REFRESH]: AuthEventHandler;
24
24
  }
25
- export type AuthenticationAccountType = 'person' | 'entity' | 'service';
25
+ export type AuthenticationAccountType = 'identity' | string;
26
+ export type TenantStatus = 'active' | 'suspended' | 'archived';
27
+ export interface TenantInfo {
28
+ id: string;
29
+ name: string;
30
+ slug: string;
31
+ parent_id: string | null;
32
+ settings: Record<string, any> | null;
33
+ status: TenantStatus;
34
+ suspended_at: string | null;
35
+ suspended_reason: string | null;
36
+ created_at: string;
37
+ updated_at: string;
38
+ }
26
39
  export type AuthenticationMethodType = 'password' | 'email_token' | 'sso' | 'otp';
27
40
  export type SSOProvider = 'google' | 'microsoft' | 'github' | 'okta' | 'apple' | 'facebook';
28
41
  export interface AuthenticationAccount {
@@ -43,11 +56,11 @@ export interface AuthenticationAccount {
43
56
  export interface PersonInfo {
44
57
  id: string;
45
58
  name: string;
46
- email?: string;
47
- phone?: string;
48
- roles: string[];
49
59
  first_name: string;
50
60
  last_name: string;
61
+ email?: string | null;
62
+ phone_number?: string | null;
63
+ roles: string[];
51
64
  }
52
65
  export interface AuthMethodInfo {
53
66
  id: string;
@@ -65,10 +78,9 @@ export interface AccountInfo {
65
78
  display_name: string;
66
79
  is_active: boolean;
67
80
  is_verified: boolean;
68
- last_login?: string;
81
+ last_login?: string | null;
69
82
  authentication_methods: AuthMethodInfo[];
70
- person?: PersonInfo;
71
- entity?: EntityInfo;
83
+ person?: PersonInfo | null;
72
84
  }
73
85
  export interface EntityInfo {
74
86
  id: string;
@@ -85,21 +97,21 @@ export interface SessionInfo {
85
97
  is_current?: boolean;
86
98
  }
87
99
  /**
88
- * Unified user representation that works for both person and entity accounts
89
- * This is the primary interface for accessing user data in the application
100
+ * Unified user representation
101
+ * All accounts are "identity" accounts that may be linked to a person
90
102
  */
91
103
  export interface User {
92
- /** Unique identifier (person_id or entity_id) */
104
+ /** Unique identifier (person_id if linked, otherwise identity_id) */
93
105
  id: string;
94
- /** Account ID */
106
+ /** Identity/Account ID */
95
107
  accountId: string;
96
108
  /** Display name */
97
109
  name: string;
98
110
  /** Email address (from person or authentication methods) */
99
111
  email?: string;
100
- /** Account type: 'person', 'entity', or 'service' */
112
+ /** Account type (always 'identity') */
101
113
  type: AuthenticationAccountType;
102
- /** User roles (only for person accounts) */
114
+ /** User roles (only when linked to person) */
103
115
  roles?: string[];
104
116
  /** Is the account active */
105
117
  isActive: boolean;
@@ -107,12 +119,10 @@ export interface User {
107
119
  isVerified: boolean;
108
120
  /** Last login timestamp */
109
121
  lastLogin?: string;
110
- /** Entity-specific info (only for entity accounts) */
111
- entityType?: string;
112
- /** Additional metadata */
113
- metadata?: Record<string, any>;
114
- /** Person-specific info (only for person accounts) */
122
+ /** Person info (if identity is linked to a person) */
115
123
  person?: PersonInfo;
124
+ /** Whether identity is linked to a person */
125
+ hasPersonLinked: boolean;
116
126
  }
117
127
  export interface RegisterRequest {
118
128
  email: string;
@@ -248,7 +258,9 @@ export type SSOInitiateResponse = AxiosResponse<{
248
258
  export type SSOCallbackResponse = AxiosResponse<AuthenticationResponse>;
249
259
  export type SSOLinkResponse = AxiosResponse<MessageResponse>;
250
260
  export type SSOUnlinkResponse = AxiosResponse<MessageResponse>;
261
+ export type GetTenantsResponse = AxiosResponse<TenantInfo[]>;
251
262
  /**
252
263
  * Extract unified user from account info
264
+ * All accounts are identities that may be linked to a person
253
265
  */
254
266
  export declare function accountToUser(account: AccountInfo | null): User | null;
package/dist/useAuth.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { App, ObjectPlugin } from 'vue';
2
- import { AccountInfo, User, NewUser, UpdatePasswordForm, UpdateAccountRequest, AuthEventMap, SSOProvider, SSOInitiateRequest, SSOCallbackRequest, SSOLinkRequest, AuthState } from './types';
2
+ import { AccountInfo, User, NewUser, UpdatePasswordForm, UpdateAccountRequest, AuthEventMap, SSOProvider, SSOInitiateRequest, SSOCallbackRequest, SSOLinkRequest, TenantInfo, AuthState } from './types';
3
3
  import { RedirectConfig, NormalizedRedirectConfig } from './types/redirect';
4
4
  interface InitParams {
5
5
  baseURL: string;
@@ -37,7 +37,7 @@ export declare function useAuth(): {
37
37
  display_name: string;
38
38
  is_active: boolean;
39
39
  is_verified: boolean;
40
- last_login?: string | undefined;
40
+ last_login?: string | null | undefined;
41
41
  authentication_methods: {
42
42
  id: string;
43
43
  type: string;
@@ -51,25 +51,19 @@ export declare function useAuth(): {
51
51
  person?: {
52
52
  id: string;
53
53
  name: string;
54
- email?: string | undefined;
55
- phone?: string | undefined;
56
- roles: string[];
57
54
  first_name: string;
58
55
  last_name: string;
59
- } | undefined;
60
- entity?: {
61
- id: string;
62
- name: string;
63
- type?: string | undefined;
64
- metadata?: Record<string, any> | undefined;
65
- } | undefined;
56
+ email?: string | null | undefined;
57
+ phone_number?: string | null | undefined;
58
+ roles: string[];
59
+ } | null | undefined;
66
60
  } | null, AccountInfo | {
67
61
  id: string;
68
62
  account_type: string;
69
63
  display_name: string;
70
64
  is_active: boolean;
71
65
  is_verified: boolean;
72
- last_login?: string | undefined;
66
+ last_login?: string | null | undefined;
73
67
  authentication_methods: {
74
68
  id: string;
75
69
  type: string;
@@ -83,27 +77,90 @@ export declare function useAuth(): {
83
77
  person?: {
84
78
  id: string;
85
79
  name: string;
86
- email?: string | undefined;
87
- phone?: string | undefined;
88
- roles: string[];
89
80
  first_name: string;
90
81
  last_name: string;
91
- } | undefined;
92
- entity?: {
93
- id: string;
94
- name: string;
95
- type?: string | undefined;
96
- metadata?: Record<string, any> | undefined;
97
- } | undefined;
82
+ email?: string | null | undefined;
83
+ phone_number?: string | null | undefined;
84
+ roles: string[];
85
+ } | null | undefined;
98
86
  } | null>;
99
87
  sso: import('./sso').SSOObject;
88
+ tenants: import('vue').Ref<{
89
+ id: string;
90
+ name: string;
91
+ slug: string;
92
+ parent_id: string | null;
93
+ settings: Record<string, any> | null;
94
+ status: import('./types').TenantStatus;
95
+ suspended_at: string | null;
96
+ suspended_reason: string | null;
97
+ created_at: string;
98
+ updated_at: string;
99
+ }[], TenantInfo[] | {
100
+ id: string;
101
+ name: string;
102
+ slug: string;
103
+ parent_id: string | null;
104
+ settings: Record<string, any> | null;
105
+ status: import('./types').TenantStatus;
106
+ suspended_at: string | null;
107
+ suspended_reason: string | null;
108
+ created_at: string;
109
+ updated_at: string;
110
+ }[]>;
111
+ currentTenant: import('vue').Ref<{
112
+ id: string;
113
+ name: string;
114
+ slug: string;
115
+ parent_id: string | null;
116
+ settings: Record<string, any> | null;
117
+ status: import('./types').TenantStatus;
118
+ suspended_at: string | null;
119
+ suspended_reason: string | null;
120
+ created_at: string;
121
+ updated_at: string;
122
+ } | null, TenantInfo | {
123
+ id: string;
124
+ name: string;
125
+ slug: string;
126
+ parent_id: string | null;
127
+ settings: Record<string, any> | null;
128
+ status: import('./types').TenantStatus;
129
+ suspended_at: string | null;
130
+ suspended_reason: string | null;
131
+ created_at: string;
132
+ updated_at: string;
133
+ } | null>;
100
134
  getFullName: () => string;
101
135
  getIsLoggedIn: () => boolean;
102
136
  getEmail: () => string;
103
137
  getRoles: () => string[];
104
- getAccountType: () => import('./types').AuthenticationAccountType;
138
+ getAccountType: () => string;
105
139
  isPersonAccount: () => boolean;
106
- isEntityAccount: () => boolean;
140
+ getTenants: () => {
141
+ id: string;
142
+ name: string;
143
+ slug: string;
144
+ parent_id: string | null;
145
+ settings: Record<string, any> | null;
146
+ status: import('./types').TenantStatus;
147
+ suspended_at: string | null;
148
+ suspended_reason: string | null;
149
+ created_at: string;
150
+ updated_at: string;
151
+ }[];
152
+ getCurrentTenant: () => {
153
+ id: string;
154
+ name: string;
155
+ slug: string;
156
+ parent_id: string | null;
157
+ settings: Record<string, any> | null;
158
+ status: import('./types').TenantStatus;
159
+ suspended_at: string | null;
160
+ suspended_reason: string | null;
161
+ created_at: string;
162
+ updated_at: string;
163
+ } | null;
107
164
  login: (credentials: {
108
165
  email: string;
109
166
  password: string;
@@ -130,5 +187,8 @@ export declare function useAuth(): {
130
187
  getSessions: (accountId?: string) => Promise<import('./types').GetSessionsResponse>;
131
188
  revokeSession: (sessionToken: string) => Promise<void>;
132
189
  revokeAllSessions: (accountId?: string) => Promise<void>;
190
+ loadTenants: () => Promise<TenantInfo[]>;
191
+ setTenant: (tenantId: string | null) => void;
192
+ switchTenant: (tenantId: string) => void;
133
193
  };
134
194
  export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/auth",
3
3
  "type": "module",
4
- "version": "1.7.92",
4
+ "version": "1.7.96",
5
5
  "description": "Bagelink auth package",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
package/src/api.ts CHANGED
@@ -37,17 +37,33 @@ import type {
37
37
  SSOCallbackResponse,
38
38
  SSOLinkResponse,
39
39
  SSOUnlinkResponse,
40
+ GetTenantsResponse,
40
41
  } from './types'
41
42
  import { createAxiosInstance } from './utils'
42
43
 
43
44
  export class AuthApi {
44
45
  private api: AxiosInstance
46
+ private currentTenantId: string | null = null
45
47
 
46
48
  constructor(baseURL: string = '') {
47
49
  this.api = createAxiosInstance(baseURL)
48
50
  this.setupInterceptors()
49
51
  }
50
52
 
53
+ /**
54
+ * Set the current tenant ID for multi-tenant requests
55
+ */
56
+ setTenantId(tenantId: string | null) {
57
+ this.currentTenantId = tenantId
58
+ }
59
+
60
+ /**
61
+ * Get the current tenant ID
62
+ */
63
+ getTenantId(): string | null {
64
+ return this.currentTenantId
65
+ }
66
+
51
67
  private setupInterceptors() {
52
68
  this.api.interceptors.request.use((config: InternalAxiosRequestConfig) => {
53
69
  // Handle password reset token from URL
@@ -56,6 +72,12 @@ export class AuthApi {
56
72
  if (resetToken !== null) {
57
73
  config.headers['X-Reset-Token'] = resetToken
58
74
  }
75
+
76
+ // Add tenant ID header if set
77
+ if (this.currentTenantId !== null) {
78
+ config.headers['X-Tenant-ID'] = this.currentTenantId
79
+ }
80
+
59
81
  return config
60
82
  })
61
83
  }
@@ -304,4 +326,15 @@ export class AuthApi {
304
326
  async cleanupSessions(): Promise<CleanupSessionsResponse> {
305
327
  return this.api.post('/authentication/cleanup-sessions', {})
306
328
  }
329
+
330
+ // ============================================
331
+ // Multi-Tenancy Methods
332
+ // ============================================
333
+
334
+ /**
335
+ * Get list of tenants the authenticated user belongs to
336
+ */
337
+ async getTenants(): Promise<GetTenantsResponse> {
338
+ return this.api.get('/tenants')
339
+ }
307
340
  }
package/src/types.ts CHANGED
@@ -34,13 +34,33 @@ export interface AuthEventMap {
34
34
  // Core Types
35
35
  // ============================================
36
36
 
37
- export type AuthenticationAccountType = 'person' | 'entity' | 'service'
37
+ // Note: All accounts are "identity" accounts. An identity may be linked to a person or not.
38
+ export type AuthenticationAccountType = 'identity' | string
38
39
 
39
- export type AuthenticationMethodType =
40
- | 'password'
41
- | 'email_token'
42
- | 'sso'
43
- | 'otp'
40
+ // ============================================
41
+ // Multi-Tenancy Types
42
+ // ============================================
43
+
44
+ export type TenantStatus = 'active' | 'suspended' | 'archived'
45
+
46
+ export interface TenantInfo {
47
+ id: string
48
+ name: string
49
+ slug: string
50
+ parent_id: string | null
51
+ settings: Record<string, any> | null
52
+ status: TenantStatus
53
+ suspended_at: string | null
54
+ suspended_reason: string | null
55
+ created_at: string
56
+ updated_at: string
57
+ }
58
+
59
+ export type AuthenticationMethodType
60
+ = | 'password'
61
+ | 'email_token'
62
+ | 'sso'
63
+ | 'otp'
44
64
 
45
65
  export type SSOProvider = 'google' | 'microsoft' | 'github' | 'okta' | 'apple' | 'facebook'
46
66
 
@@ -63,11 +83,11 @@ export interface AuthenticationAccount {
63
83
  export interface PersonInfo {
64
84
  id: string
65
85
  name: string
66
- email?: string
67
- phone?: string
68
- roles: string[]
69
86
  first_name: string
70
87
  last_name: string
88
+ email?: string | null
89
+ phone_number?: string | null
90
+ roles: string[]
71
91
  }
72
92
 
73
93
  export interface AuthMethodInfo {
@@ -87,12 +107,13 @@ export interface AccountInfo {
87
107
  display_name: string
88
108
  is_active: boolean
89
109
  is_verified: boolean
90
- last_login?: string
110
+ last_login?: string | null
91
111
  authentication_methods: AuthMethodInfo[]
92
- person?: PersonInfo
93
- entity?: EntityInfo
112
+ person?: PersonInfo | null
94
113
  }
95
114
 
115
+ // Note: EntityInfo kept for backward compatibility
116
+ // Current API no longer returns entity in AccountInfo
96
117
  export interface EntityInfo {
97
118
  id: string
98
119
  name: string
@@ -114,21 +135,21 @@ export interface SessionInfo {
114
135
  // ============================================
115
136
 
116
137
  /**
117
- * Unified user representation that works for both person and entity accounts
118
- * This is the primary interface for accessing user data in the application
138
+ * Unified user representation
139
+ * All accounts are "identity" accounts that may be linked to a person
119
140
  */
120
141
  export interface User {
121
- /** Unique identifier (person_id or entity_id) */
142
+ /** Unique identifier (person_id if linked, otherwise identity_id) */
122
143
  id: string
123
- /** Account ID */
144
+ /** Identity/Account ID */
124
145
  accountId: string
125
146
  /** Display name */
126
147
  name: string
127
148
  /** Email address (from person or authentication methods) */
128
149
  email?: string
129
- /** Account type: 'person', 'entity', or 'service' */
150
+ /** Account type (always 'identity') */
130
151
  type: AuthenticationAccountType
131
- /** User roles (only for person accounts) */
152
+ /** User roles (only when linked to person) */
132
153
  roles?: string[]
133
154
  /** Is the account active */
134
155
  isActive: boolean
@@ -136,12 +157,10 @@ export interface User {
136
157
  isVerified: boolean
137
158
  /** Last login timestamp */
138
159
  lastLogin?: string
139
- /** Entity-specific info (only for entity accounts) */
140
- entityType?: string
141
- /** Additional metadata */
142
- metadata?: Record<string, any>
143
- /** Person-specific info (only for person accounts) */
160
+ /** Person info (if identity is linked to a person) */
144
161
  person?: PersonInfo
162
+ /** Whether identity is linked to a person */
163
+ hasPersonLinked: boolean
145
164
  }
146
165
 
147
166
  // ============================================
@@ -302,6 +321,7 @@ export type SSOInitiateResponse = AxiosResponse<{ authorization_url: string }>
302
321
  export type SSOCallbackResponse = AxiosResponse<AuthenticationResponse>
303
322
  export type SSOLinkResponse = AxiosResponse<MessageResponse>
304
323
  export type SSOUnlinkResponse = AxiosResponse<MessageResponse>
324
+ export type GetTenantsResponse = AxiosResponse<TenantInfo[]>
305
325
 
306
326
  // ============================================
307
327
  // Helper Functions (exported for convenience)
@@ -309,45 +329,33 @@ export type SSOUnlinkResponse = AxiosResponse<MessageResponse>
309
329
 
310
330
  /**
311
331
  * Extract unified user from account info
332
+ * All accounts are identities that may be linked to a person
312
333
  */
313
334
  export function accountToUser(account: AccountInfo | null): User | null {
314
335
  if (account === null) { return null }
315
336
 
316
- // Person account - most common case
317
- if (account.person !== undefined) {
318
- return {
319
- id: account.person.id,
320
- accountId: account.id,
321
- name: account.person.name,
322
- email: account.person.email,
323
- type: account.account_type as AuthenticationAccountType,
324
- roles: account.person.roles,
325
- isActive: account.is_active,
326
- isVerified: account.is_verified,
327
- person: account.person,
328
- lastLogin: account.last_login,
329
- }
330
- }
337
+ const hasPersonLinked = account.person !== undefined && account.person !== null
331
338
 
332
- // Entity account
333
- if (account.entity !== undefined) {
339
+ // Identity linked to person - use person data
340
+ if (hasPersonLinked) {
334
341
  return {
335
- id: account.entity.id,
342
+ id: account.person!.id,
336
343
  accountId: account.id,
337
- name: account.entity.name,
344
+ name: account.person!.name,
345
+ email: account.person!.email ?? undefined,
338
346
  type: account.account_type as AuthenticationAccountType,
347
+ roles: account.person!.roles,
339
348
  isActive: account.is_active,
340
349
  isVerified: account.is_verified,
341
- lastLogin: account.last_login,
342
- entityType: account.entity.type,
343
- metadata: account.entity.metadata,
350
+ person: account.person!,
351
+ lastLogin: account.last_login ?? undefined,
352
+ hasPersonLinked: true,
344
353
  }
345
354
  }
346
355
 
347
- // Fallback - use account info directly
348
- // Extract email from authentication methods
356
+ // Identity without person link - extract data from authentication methods
349
357
  const emailMethod = account.authentication_methods.find(
350
- m => m.type === 'password' || m.type === 'email_token',
358
+ m => m.type === 'password' || m.type === 'email_token' || m.type === 'sso',
351
359
  )
352
360
 
353
361
  return {
@@ -358,6 +366,7 @@ export function accountToUser(account: AccountInfo | null): User | null {
358
366
  type: account.account_type as AuthenticationAccountType,
359
367
  isActive: account.is_active,
360
368
  isVerified: account.is_verified,
361
- lastLogin: account.last_login,
369
+ lastLogin: account.last_login ?? undefined,
370
+ hasPersonLinked: false,
362
371
  }
363
372
  }
package/src/useAuth.ts CHANGED
@@ -10,6 +10,7 @@ import type {
10
10
  SSOInitiateRequest,
11
11
  SSOCallbackRequest,
12
12
  SSOLinkRequest,
13
+ TenantInfo,
13
14
  } from './types'
14
15
  import type { RedirectConfig, NormalizedRedirectConfig } from './types/redirect'
15
16
  import { ref, computed } from 'vue'
@@ -26,6 +27,8 @@ let redirectConfig: NormalizedRedirectConfig | null = null
26
27
  let autoRedirectRouter: any = null // Router instance for auto-redirect
27
28
  let cachedAuthGuard: any = null // Cached router guard
28
29
  const accountInfo = ref<AccountInfo | null>(null)
30
+ const tenants = ref<TenantInfo[]>([])
31
+ const currentTenant = ref<TenantInfo | null>(null)
29
32
 
30
33
  interface InitParams {
31
34
  baseURL: string
@@ -262,17 +265,16 @@ export function useAuth() {
262
265
  }
263
266
 
264
267
  const getAccountType = () => {
265
- return user.value?.type ?? 'person'
268
+ return user.value?.type ?? 'identity'
266
269
  }
267
270
 
271
+ /**
272
+ * Check if identity is linked to a person
273
+ * @returns true if the identity has a person linked
274
+ */
268
275
  const isPersonAccount = () => {
269
- return user.value?.type === 'person'
270
- }
271
-
272
- const isEntityAccount = () => {
273
- return user.value?.type === 'entity'
276
+ return user.value?.hasPersonLinked === true
274
277
  }
275
-
276
278
  // Actions
277
279
  async function logout() {
278
280
  // Call logout API
@@ -315,6 +317,55 @@ export function useAuth() {
315
317
  }
316
318
  }
317
319
 
320
+ async function loadTenants(): Promise<TenantInfo[]> {
321
+ try {
322
+ const { data } = await api.getTenants()
323
+ tenants.value = data
324
+
325
+ // Auto-select first tenant if none selected and tenants available
326
+ if (currentTenant.value === null && tenants.value.length > 0) {
327
+ const firstActiveTenant = tenants.value.find(t => t.status === 'active')
328
+ if (firstActiveTenant !== undefined) {
329
+ setTenant(firstActiveTenant.id)
330
+ }
331
+ }
332
+
333
+ return tenants.value
334
+ } catch {
335
+ // If 404 or other error, assume multi-tenancy not supported
336
+ tenants.value = []
337
+ return []
338
+ }
339
+ }
340
+
341
+ function setTenant(tenantId: string | null) {
342
+ if (tenantId === null) {
343
+ currentTenant.value = null
344
+ api.setTenantId(null)
345
+ return
346
+ }
347
+
348
+ const tenant = tenants.value.find(t => t.id === tenantId)
349
+ if (tenant !== undefined) {
350
+ currentTenant.value = tenant
351
+ api.setTenantId(tenantId)
352
+ } else {
353
+ throw new Error(`Tenant with ID ${tenantId} not found`)
354
+ }
355
+ }
356
+
357
+ function switchTenant(tenantId: string): void {
358
+ setTenant(tenantId)
359
+ }
360
+
361
+ function getTenants() {
362
+ return tenants.value
363
+ }
364
+
365
+ function getCurrentTenant() {
366
+ return currentTenant.value
367
+ }
368
+
318
369
  async function signup(newUser: NewUser) {
319
370
  // Check password match if password is provided
320
371
  const hasPassword = newUser.password !== undefined && newUser.password.length > 0
@@ -478,6 +529,10 @@ export function useAuth() {
478
529
  // SSO Providers (ready to use!)
479
530
  sso,
480
531
 
532
+ // Multi-Tenancy State
533
+ tenants,
534
+ currentTenant,
535
+
481
536
  // Getters
482
537
  getFullName,
483
538
  getIsLoggedIn,
@@ -485,7 +540,8 @@ export function useAuth() {
485
540
  getRoles,
486
541
  getAccountType,
487
542
  isPersonAccount,
488
- isEntityAccount,
543
+ getTenants,
544
+ getCurrentTenant,
489
545
 
490
546
  // Authentication Actions
491
547
  login,
@@ -523,5 +579,10 @@ export function useAuth() {
523
579
  getSessions,
524
580
  revokeSession,
525
581
  revokeAllSessions,
582
+
583
+ // Multi-Tenancy Actions
584
+ loadTenants,
585
+ setTenant,
586
+ switchTenant,
526
587
  }
527
588
  }