@marianmeres/ownsuite 2.0.0 → 2.2.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.
@@ -0,0 +1,182 @@
1
+ /**
2
+ * @module types/auth
3
+ *
4
+ * Types for the auth / profile / session managers that extend ownsuite with
5
+ * a first-class identity lifecycle (register, login, logout, OAuth link/
6
+ * unlink, email verification, password reset, delete account, profile edit).
7
+ *
8
+ * Design notes:
9
+ * - The adapter pattern mirrors `OwnedCollectionAdapter` — consumers plug in
10
+ * an implementation; no HTTP code lives in managers.
11
+ * - The `SessionManager` is the single source of truth for the JWT and the
12
+ * current subject. Adapters never hold auth state.
13
+ * - OAuth initiation returns a URL — the popup / redirect dance is handled
14
+ * by the `openOAuthPopup` helper (see `oauth/popup.ts`) rather than the
15
+ * adapter itself.
16
+ */
17
+ import type { OwnsuiteContext } from "./state.js";
18
+ /** Lifecycle state of the current session. */
19
+ export type SessionStatus =
20
+ /** No JWT. Either never logged in or explicitly logged out. */
21
+ "anonymous"
22
+ /** JWT present, subject loaded, login succeeded. */
23
+ | "authenticated"
24
+ /**
25
+ * Account exists and credentials were correct BUT the server is blocking
26
+ * login because the email is not yet verified. UI should surface "check
27
+ * your email" without a second round-trip. Emitted when `auth.login()`
28
+ * or `auth.register()` hits the verification gate.
29
+ */
30
+ | "unverified";
31
+ /**
32
+ * Minimal subject shape exposed to consumers. Matches what stack-account's
33
+ * `/me` returns plus the fields needed for role-gating.
34
+ */
35
+ export interface SessionSubject {
36
+ id: string;
37
+ email: string;
38
+ roles: string[];
39
+ isVerified: boolean;
40
+ /** Whether the account has a password (OAuth-only accounts do not). */
41
+ hasPassword: boolean;
42
+ }
43
+ /** Observable session state — stored by the SessionManager. */
44
+ export interface SessionState {
45
+ status: SessionStatus;
46
+ subject: SessionSubject | null;
47
+ jwt: string | null;
48
+ /** Unix-seconds expiry (optional — not every server shape returns one). */
49
+ expiresAt: number | null;
50
+ }
51
+ /**
52
+ * Pluggable storage for persisting session state across reloads. Consumers
53
+ * can pass "local" / "session" / "memory" or supply their own object
54
+ * matching this interface (useful for Tauri, WebExtensions, SSR guards).
55
+ */
56
+ export interface SessionStorage {
57
+ get(key: string): string | null;
58
+ set(key: string, value: string): void;
59
+ del(key: string): void;
60
+ }
61
+ export type SessionStorageType = "local" | "session" | "memory" | SessionStorage;
62
+ export type OAuthProvider = "google" | "facebook" | "apple" | "twitter";
63
+ export interface OAuthConnection {
64
+ provider: OAuthProvider;
65
+ display_name?: string;
66
+ avatar_url?: string;
67
+ email?: string;
68
+ }
69
+ /** Action verb for the OAuth init URL — `login` creates/looks-up an account,
70
+ * `link` attaches the provider to the currently authenticated subject. */
71
+ export type OAuthAction = "login" | "link";
72
+ export interface OAuthInitOptions {
73
+ action: OAuthAction;
74
+ /** Where to redirect after the provider callback (server honours this). */
75
+ redirect?: string;
76
+ /** Language code forwarded to the server for error-page localization. */
77
+ lang?: string;
78
+ /** `"popup"` (default) or `"redirect"` — the manager uses this to decide
79
+ * whether to open a popup and wait for a postMessage, or redirect the
80
+ * top window. */
81
+ mode?: "popup" | "redirect";
82
+ /** Same semantics as {@link AuthActionOptions.remember} — pins the
83
+ * resulting session to `localStorage` (`true`) or `sessionStorage`
84
+ * (`false`). Only meaningful for `action: "login"`. */
85
+ remember?: boolean;
86
+ }
87
+ /**
88
+ * Options accepted by `AuthManager.login` / `register` /
89
+ * `handleOAuthCallback` to express per-login storage preference
90
+ * ("Remember me").
91
+ */
92
+ export interface AuthActionOptions {
93
+ /** `true` → persist the resulting session to `localStorage` (survives
94
+ * browser restart).
95
+ * `false` → persist to `sessionStorage` (dies with the tab).
96
+ * `undefined` → use the `SessionManager`'s configured default backend.
97
+ *
98
+ * Silently ignored when the `SessionManager` was constructed with a
99
+ * custom `SessionStorage` object — the single custom backend is used
100
+ * regardless. */
101
+ remember?: boolean;
102
+ }
103
+ /**
104
+ * Uniform result shape for `register` / `login` / OAuth success. When the
105
+ * server returns `requiresVerification: true` (i.e. the verification gate is
106
+ * on and the email is not yet verified), the JWT will be absent and the
107
+ * manager flips session.status to "unverified".
108
+ */
109
+ export interface AuthTokenResult {
110
+ jwt?: string;
111
+ email: string;
112
+ roles: string[];
113
+ isVerified?: boolean;
114
+ validFrom?: number;
115
+ validUntil?: number;
116
+ /** Set by the server when login auto-login is declined for a not-yet-
117
+ * verified account. Mutually exclusive with jwt in practice. */
118
+ requiresVerification?: boolean;
119
+ }
120
+ export interface ProfileResult {
121
+ email: string;
122
+ roles: string[];
123
+ isVerified: boolean;
124
+ hasPassword: boolean;
125
+ oauthConnections: OAuthConnection[];
126
+ }
127
+ export interface AuthAdapter {
128
+ register(input: {
129
+ email: string;
130
+ password: string;
131
+ password_confirm: string;
132
+ roles?: string[];
133
+ /** Optional extras — consumer forwards any configured
134
+ * registrationFields the server expects. */
135
+ extras?: Record<string, unknown>;
136
+ }, ctx: OwnsuiteContext): Promise<AuthTokenResult>;
137
+ login(input: {
138
+ email: string;
139
+ password: string;
140
+ }, ctx: OwnsuiteContext): Promise<AuthTokenResult>;
141
+ /** Best-effort server-side revocation. Must not throw on
142
+ * already-anonymous state. */
143
+ logout(ctx: OwnsuiteContext): Promise<void>;
144
+ /** Sync URL builder. The manager opens this URL in a popup (default) or
145
+ * redirects the top window depending on mode. */
146
+ oauthInitUrl(provider: OAuthProvider, opts: OAuthInitOptions, ctx: OwnsuiteContext): string;
147
+ /** For the redirect-mode callback path: extract result from the current
148
+ * URL (or a server-rendered payload) and return it. Popup-mode uses the
149
+ * `openOAuthPopup` helper directly and does not call this. */
150
+ handleOAuthCallback?(ctx: OwnsuiteContext): Promise<AuthTokenResult>;
151
+ resendVerification(input: {
152
+ email: string;
153
+ lang?: string;
154
+ }, ctx: OwnsuiteContext): Promise<void>;
155
+ requestPasswordReset(input: {
156
+ email: string;
157
+ lang?: string;
158
+ }, ctx: OwnsuiteContext): Promise<void>;
159
+ changePassword(input: {
160
+ /** Required for authenticated self-change. */
161
+ current_password?: string;
162
+ new_password: string;
163
+ confirm_password: string;
164
+ /** Required for token-based reset. */
165
+ token?: string;
166
+ }, ctx: OwnsuiteContext): Promise<void>;
167
+ deleteAccount(input: {
168
+ password?: string;
169
+ confirm?: boolean;
170
+ }, ctx: OwnsuiteContext): Promise<{
171
+ deleted: true;
172
+ }>;
173
+ }
174
+ export interface ProfileAdapter {
175
+ get(ctx: OwnsuiteContext): Promise<ProfileResult>;
176
+ update(input: {
177
+ email?: string;
178
+ current_password?: string;
179
+ }, ctx: OwnsuiteContext): Promise<ProfileResult>;
180
+ listOAuth(ctx: OwnsuiteContext): Promise<OAuthConnection[]>;
181
+ unlinkOAuth(provider: OAuthProvider, ctx: OwnsuiteContext): Promise<void>;
182
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @module types/auth
3
+ *
4
+ * Types for the auth / profile / session managers that extend ownsuite with
5
+ * a first-class identity lifecycle (register, login, logout, OAuth link/
6
+ * unlink, email verification, password reset, delete account, profile edit).
7
+ *
8
+ * Design notes:
9
+ * - The adapter pattern mirrors `OwnedCollectionAdapter` — consumers plug in
10
+ * an implementation; no HTTP code lives in managers.
11
+ * - The `SessionManager` is the single source of truth for the JWT and the
12
+ * current subject. Adapters never hold auth state.
13
+ * - OAuth initiation returns a URL — the popup / redirect dance is handled
14
+ * by the `openOAuthPopup` helper (see `oauth/popup.ts`) rather than the
15
+ * adapter itself.
16
+ */
17
+ export {};
@@ -4,6 +4,7 @@
4
4
  * Event type definitions for the ownsuite event system.
5
5
  */
6
6
  import type { DomainError, DomainState } from "./state.js";
7
+ import type { OAuthConnection, OAuthProvider, SessionState } from "./auth.js";
7
8
  /**
8
9
  * Domain identifier in ownsuite is an arbitrary string (the collection name
9
10
  * or any label the consumer chose), unlike ecsuite's fixed enum of six
@@ -11,7 +12,7 @@ import type { DomainError, DomainState } from "./state.js";
11
12
  */
12
13
  export type DomainName = string;
13
14
  /** Event types emitted by the suite. */
14
- export type OwnsuiteEventType = "domain:state:changed" | "domain:error" | "domain:synced" | "own:list:fetched" | "own:row:fetched" | "own:row:created" | "own:row:updated" | "own:row:deleted";
15
+ export type OwnsuiteEventType = "domain:state:changed" | "domain:error" | "domain:synced" | "own:list:fetched" | "own:row:fetched" | "own:row:created" | "own:row:updated" | "own:row:deleted" | "auth:register" | "auth:login" | "auth:logout" | "auth:session:changed" | "auth:verification:required" | "profile:updated" | "oauth:linked" | "oauth:unlinked";
15
16
  /** Base event data. */
16
17
  export interface OwnsuiteEventBase {
17
18
  /** Event timestamp */
@@ -59,5 +60,43 @@ export interface RowDeletedEvent extends OwnsuiteEventBase {
59
60
  type: "own:row:deleted";
60
61
  rowId: string;
61
62
  }
63
+ export interface AuthEventBase {
64
+ timestamp: number;
65
+ }
66
+ export interface AuthRegisterEvent extends AuthEventBase {
67
+ type: "auth:register";
68
+ email: string;
69
+ /** True when the server requires email verification (no auto-login). */
70
+ requiresVerification: boolean;
71
+ }
72
+ export interface AuthLoginEvent extends AuthEventBase {
73
+ type: "auth:login";
74
+ email: string;
75
+ }
76
+ export interface AuthLogoutEvent extends AuthEventBase {
77
+ type: "auth:logout";
78
+ /** Id of the subject that just logged out, if known. */
79
+ subjectId?: string;
80
+ }
81
+ export interface AuthSessionChangedEvent extends AuthEventBase {
82
+ type: "auth:session:changed";
83
+ session: SessionState;
84
+ }
85
+ export interface AuthVerificationRequiredEvent extends AuthEventBase {
86
+ type: "auth:verification:required";
87
+ email: string;
88
+ }
89
+ export interface ProfileUpdatedEvent extends AuthEventBase {
90
+ type: "profile:updated";
91
+ email: string;
92
+ }
93
+ export interface OAuthLinkedEvent extends AuthEventBase {
94
+ type: "oauth:linked";
95
+ connection: OAuthConnection;
96
+ }
97
+ export interface OAuthUnlinkedEvent extends AuthEventBase {
98
+ type: "oauth:unlinked";
99
+ provider: OAuthProvider;
100
+ }
62
101
  /** All event types union. */
63
- export type OwnsuiteEvent = StateChangedEvent | ErrorEvent | SyncedEvent | ListFetchedEvent | RowFetchedEvent | RowCreatedEvent | RowUpdatedEvent | RowDeletedEvent;
102
+ export type OwnsuiteEvent = StateChangedEvent | ErrorEvent | SyncedEvent | ListFetchedEvent | RowFetchedEvent | RowCreatedEvent | RowUpdatedEvent | RowDeletedEvent | AuthRegisterEvent | AuthLoginEvent | AuthLogoutEvent | AuthSessionChangedEvent | AuthVerificationRequiredEvent | ProfileUpdatedEvent | OAuthLinkedEvent | OAuthUnlinkedEvent;
@@ -1,3 +1,4 @@
1
1
  export * from "./state.js";
2
2
  export * from "./events.js";
3
3
  export * from "./adapter.js";
4
+ export * from "./auth.js";
package/dist/types/mod.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export * from "./state.js";
2
2
  export * from "./events.js";
3
3
  export * from "./adapter.js";
4
+ export * from "./auth.js";
@@ -42,6 +42,13 @@ export interface DomainStateWrapper<T> {
42
42
  export interface OwnsuiteContext {
43
43
  /** Hint — not used for authorization. The server is authoritative. */
44
44
  subjectId?: string;
45
+ /**
46
+ * JWT to pass through to adapters as the `Authorization: Bearer <jwt>`
47
+ * credential. Managed by the `SessionManager` — consumers don't set this
48
+ * directly. When present, adapters must forward it. When absent, the
49
+ * server will treat the request as anonymous.
50
+ */
51
+ jwt?: string;
45
52
  /**
46
53
  * Per-operation abort signal injected by the manager. Adapters should
47
54
  * forward this to `fetch(url, { signal: ctx.signal })`. Aborts fire on
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/ownsuite",
3
- "version": "2.0.0",
3
+ "version": "2.2.1",
4
4
  "type": "module",
5
5
  "main": "dist/mod.js",
6
6
  "types": "dist/mod.d.ts",