@bkazemi/verily 0.8.1 → 0.9.0

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.
@@ -5,6 +5,8 @@ export declare const handoffMs: number;
5
5
  /** How long a site session lasts once the handoff lands. */
6
6
  export declare const sessionMs = 3600000;
7
7
  export declare const sessionCookie = "verily_site";
8
+ /** What an operator may change about a stored site. Absent lists mean every one. */
9
+ export type Settings = Pick<Site, 'authorizeUrl' | 'returnUrl' | 'providers' | 'visibility'>;
8
10
  /** A site that sends its users here instead of hosting Verily itself. */
9
11
  export interface Site {
10
12
  /** Prefixes its subjects' ids, and names it in every token. */
@@ -18,6 +20,9 @@ export interface Site {
18
20
  returnUrl: string;
19
21
  /** Shared with the site's backend. Signs handoffs one way and results the other. */
20
22
  key: string;
23
+ /** The key before the last rotation, still accepted until `previousUntil`. */
24
+ previousKey?: string;
25
+ previousUntil?: number;
21
26
  /** The provider ids its holders may use. Absent means every one this instance offers. */
22
27
  providers?: string[];
23
28
  /** The visibilities its holders may choose. Absent means both. */
@@ -49,6 +54,12 @@ export interface SiteSession {
49
54
  /** The flow whose result this session returned with. No other flow's result is signed. */
50
55
  returned?: string;
51
56
  operation?: Operation;
57
+ /**
58
+ * The hash of the site key that signed the token this session was opened with. Its result
59
+ * is signed with that same key while the key is accepted, even if it has been replaced
60
+ * since, because the backend that started the handoff may not have the new key yet.
61
+ */
62
+ signer?: string;
52
63
  /**
53
64
  * Opened for the dialog on the site's own page, which reads how each flow ended for
54
65
  * itself. No flow returns to the site from it, so no flow's end closes it, and the
@@ -56,6 +67,49 @@ export interface SiteSession {
56
67
  */
57
68
  dialog?: true;
58
69
  }
70
+ /** What is kept of a stored site once it is gone: that its id was used, when, and why. */
71
+ export interface Retired {
72
+ retiredAt: number;
73
+ /** `deleted` by an operator, or `lapsed` after a month suspended. */
74
+ reason: 'deleted' | 'lapsed';
75
+ }
76
+ /** How long a stored site stays suspended before it is deleted. */
77
+ export declare const lapseMs: number;
78
+ /**
79
+ * What the instance owner has set for a stored site. Kept apart from the site itself, so
80
+ * nothing its operators change can reach it.
81
+ */
82
+ export interface Controls {
83
+ /** When the owner suspended the site. Absent while the owner has not. */
84
+ suspendedAt?: number;
85
+ /** Requests a minute for the site, in place of `limits.registered`. */
86
+ limit?: number;
87
+ /** When the owner let the site's holders use YouTube. Absent while it is withheld. */
88
+ youtubeApprovedAt?: number;
89
+ }
90
+ /** Where the last DNS check of a stored site is kept, under its id. */
91
+ export declare const dnsPrefix = "site/dns/";
92
+ /** What the last DNS check of a stored site found. */
93
+ export interface DnsCheck {
94
+ /** When the DNS was last asked, whether or not it answered. */
95
+ attemptedAt: number;
96
+ /** When the DNS last answered. Absent if it never has. */
97
+ checkedAt?: number;
98
+ /** When the site's `verily-verifier` record was first found missing. Absent while it is there. */
99
+ absentSince?: number;
100
+ }
101
+ /** Someone who may manage a stored site. Only a dashboard key, for now, kept hashed. */
102
+ export interface Operator {
103
+ kind: 'key';
104
+ hash: string;
105
+ addedAt: number;
106
+ }
107
+ /** A site's id: what prefixes its subjects and names it in every token. */
108
+ export declare const validId: (id: unknown) => id is string;
109
+ /** A key: base64url, of at least 32 bytes. */
110
+ export declare const validKey: (key: unknown) => key is string;
111
+ /** A site entry checked field by field, without its key. Throws on the first thing wrong. */
112
+ export declare function checked(entry: Record<string, unknown>): Omit<Site, 'key'>;
59
113
  /**
60
114
  * The registered sites, from the `SITES` variable, each with its key from the secret
61
115
  * `SITE_<ID>_KEY`. Anything wrong in the list refuses to start rather than serving a site
@@ -68,6 +122,8 @@ export declare function sign(key: string, payload: object): string;
68
122
  export declare function peek(token: string): Record<string, unknown> | undefined;
69
123
  /** The payload of a token whose MAC verifies under `key`, compared in constant time. */
70
124
  export declare function open(key: string, token: string): Record<string, unknown> | undefined;
125
+ /** How long a replaced site key is still accepted, so the site has time to deploy the new one. */
126
+ export declare const rotationMs: number;
71
127
  /**
72
128
  * Handoffs from registered sites, the sessions they open, and the results sent back. The
73
129
  * site vouches for its signed-in user; this is where that is checked, and a handoff is
@@ -75,8 +131,80 @@ export declare function open(key: string, token: string): Record<string, unknown
75
131
  */
76
132
  export declare class Sites {
77
133
  private readonly storage;
78
- readonly sites: Map<string, Site>;
79
- constructor(storage: Store, sites: Map<string, Site>);
134
+ private readonly pinned;
135
+ /**
136
+ * Every site served: the pinned ones, then the ones kept in the store, as `refresh()`
137
+ * last read them.
138
+ */
139
+ sites: Map<string, Site>;
140
+ /**
141
+ * Stored sites whose DNS no longer names this instance, or that the owner suspended. They
142
+ * start nothing new, but their backends can still read their connections, so they can
143
+ * move elsewhere.
144
+ */
145
+ suspended: Set<string>;
146
+ /** What the instance owner has set for each stored site, as `refresh()` last read it. */
147
+ controls: Map<string, Controls>;
148
+ /**
149
+ * `pinned` are the sites the host configures, from `SITES`. They cannot be changed here,
150
+ * and a stored site with the same id or origin is not served.
151
+ */
152
+ constructor(storage: Store, pinned: Map<string, Site>);
153
+ /**
154
+ * Reads the stored sites again. The instance does this on every request, so a site
155
+ * registered, changed or removed by another process is served as it now is.
156
+ */
157
+ refresh(): Promise<void>;
158
+ /** The sites kept in the store and served, which are the ones whose DNS is checked. */
159
+ stored(): Site[];
160
+ /**
161
+ * Requests a minute for a site, counted once for its holders and once for its backend's
162
+ * reads. Undefined for a pinned site, which gets the instance's default.
163
+ */
164
+ limit(id: string): number | undefined;
165
+ /**
166
+ * The provider ids a site's holders may use, from those the instance `offers`. A stored
167
+ * site goes without YouTube until the owner approves it. Undefined means every one.
168
+ */
169
+ providers(site: Site, offers: string[]): string[] | undefined;
170
+ /**
171
+ * The provider ids the owner has not approved a site for, whichever its operators chose.
172
+ * None for a pinned site.
173
+ */
174
+ withheld(id: string): string[];
175
+ /** Changes what the instance owner has set for a stored site. */
176
+ control(id: string, change: (controls: Controls) => Controls): Promise<void>;
177
+ /** A site that may start something new: served, and not suspended. */
178
+ private active;
179
+ /**
180
+ * Keeps a new site in the store with its first operator. Refused if the entry is
181
+ * malformed, or its origin belongs to another site, or its id is in use or has ever been.
182
+ */
183
+ register(site: Site, first: Operator): Promise<void>;
184
+ /**
185
+ * Changes a stored site's settings. The rest of the site, its keys above all, is read in
186
+ * the same transaction and kept as it is, so a change made from a page loaded before a
187
+ * key was replaced cannot bring the old key back. Refused if the result is malformed.
188
+ */
189
+ update(id: string, settings: Settings): Promise<void>;
190
+ /**
191
+ * Gives a stored site a new key and returns it. With `keep`, the old key is still
192
+ * accepted for `rotationMs`, for a routine change. Without it, the old key stops working
193
+ * at once, for a key that may have leaked. Any key before the old one stops either way.
194
+ *
195
+ * With `from`, the hash of the key the caller saw, nothing changes unless that is still
196
+ * the site's key, so a form sent twice replaces the key once. Returns undefined then.
197
+ */
198
+ rotate(id: string, keep: boolean, from?: string): Promise<string | undefined>;
199
+ /** Stops accepting a stored site's previous key, once the new one is deployed. */
200
+ retirePreviousKey(id: string): Promise<void>;
201
+ /**
202
+ * Removes a stored site with its operators, DNS state and owner's controls, and keeps its
203
+ * id so no other site is ever given it. Its users' records stay, but their site starts nothing new.
204
+ */
205
+ retire(id: string, reason: Retired['reason']): Promise<void>;
206
+ /** The operators of a stored site. */
207
+ operators(id: string): Promise<Operator[]>;
80
208
  /**
81
209
  * Starts a handoff: a state stored with the site and purpose, set in this browser, and
82
210
  * sent to the site to sign into its token. Returns where to go and the cookie to set.
@@ -150,14 +278,17 @@ export declare class Sites {
150
278
  * so of a flow ending and a disconnect starting exactly one goes on.
151
279
  */
152
280
  private claim;
153
- /** The signed result a holder carries back to their site. */
281
+ /**
282
+ * The signed result a holder carries back to their site, under the key that opened its
283
+ * session while that key is still accepted, and under the site's key otherwise.
284
+ */
154
285
  returnUrl(site: Site, local: LocalAccount, txn: string, result: {
155
286
  operation: string;
156
287
  outcome: string;
157
288
  connection?: string;
158
289
  visibility?: Visibility;
159
290
  finishedAt: number;
160
- }): string;
291
+ }, signer?: string): string;
161
292
  /**
162
293
  * Records a disconnect or a mark as pending on the session before anything is changed, so
163
294
  * a retry knows what it is finishing. A session already carrying one only resumes that one.