@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.
- package/AGENTS.md +88 -9
- package/API.md +342 -1
- package/README.md +68 -0
- package/dist/adapters/mock-auth.d.ts +38 -0
- package/dist/adapters/mock-auth.js +237 -0
- package/dist/adapters/mod.d.ts +2 -0
- package/dist/adapters/mod.js +2 -0
- package/dist/adapters/stack-account.d.ts +38 -0
- package/dist/adapters/stack-account.js +149 -0
- package/dist/domains/auth.d.ts +85 -0
- package/dist/domains/auth.js +224 -0
- package/dist/domains/mod.d.ts +3 -0
- package/dist/domains/mod.js +3 -0
- package/dist/domains/profile.d.ts +62 -0
- package/dist/domains/profile.js +170 -0
- package/dist/domains/session.d.ts +92 -0
- package/dist/domains/session.js +323 -0
- package/dist/mod.d.ts +1 -0
- package/dist/mod.js +1 -0
- package/dist/oauth/popup.d.ts +64 -0
- package/dist/oauth/popup.js +104 -0
- package/dist/ownsuite.d.ts +21 -0
- package/dist/ownsuite.js +86 -0
- package/dist/types/auth.d.ts +182 -0
- package/dist/types/auth.js +17 -0
- package/dist/types/events.d.ts +41 -2
- package/dist/types/mod.d.ts +1 -0
- package/dist/types/mod.js +1 -0
- package/dist/types/state.d.ts +7 -0
- package/package.json +1 -1
|
@@ -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 {};
|
package/dist/types/events.d.ts
CHANGED
|
@@ -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;
|
package/dist/types/mod.d.ts
CHANGED
package/dist/types/mod.js
CHANGED
package/dist/types/state.d.ts
CHANGED
|
@@ -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
|