@bkazemi/verily 0.1.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.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +503 -0
  3. package/dist/browser/badge.d.ts +39 -0
  4. package/dist/browser/badge.js +440 -0
  5. package/dist/browser/connect-dialog.d.ts +83 -0
  6. package/dist/browser/connect-dialog.js +514 -0
  7. package/dist/browser/evidence-dialog.d.ts +59 -0
  8. package/dist/browser/evidence-dialog.js +541 -0
  9. package/dist/browser/index.d.ts +59 -0
  10. package/dist/browser/index.js +809 -0
  11. package/dist/browser/logo.d.ts +2 -0
  12. package/dist/browser/logo.js +17 -0
  13. package/dist/browser/mark.d.ts +19 -0
  14. package/dist/browser/mark.js +77 -0
  15. package/dist/browser/provider-mark.d.ts +6 -0
  16. package/dist/browser/provider-mark.js +99 -0
  17. package/dist/browser/signed.d.ts +40 -0
  18. package/dist/browser/signed.js +219 -0
  19. package/dist/browser.d.ts +3 -0
  20. package/dist/browser.js +3 -0
  21. package/dist/core/index.d.ts +454 -0
  22. package/dist/core/index.js +128 -0
  23. package/dist/core/signed.d.ts +49 -0
  24. package/dist/core/signed.js +113 -0
  25. package/dist/index.d.ts +5 -0
  26. package/dist/index.js +5 -0
  27. package/dist/logo.d.ts +21 -0
  28. package/dist/logo.js +38 -0
  29. package/dist/postgres/index.d.ts +12 -0
  30. package/dist/postgres/index.js +76 -0
  31. package/dist/server/addresses.d.ts +2 -0
  32. package/dist/server/addresses.js +61 -0
  33. package/dist/server/body.d.ts +11 -0
  34. package/dist/server/body.js +49 -0
  35. package/dist/server/copy.d.ts +8 -0
  36. package/dist/server/copy.js +42 -0
  37. package/dist/server/discord.d.ts +7 -0
  38. package/dist/server/discord.js +69 -0
  39. package/dist/server/email-logo.d.ts +11 -0
  40. package/dist/server/email-logo.js +122 -0
  41. package/dist/server/email.d.ts +46 -0
  42. package/dist/server/email.js +145 -0
  43. package/dist/server/escape.d.ts +2 -0
  44. package/dist/server/escape.js +2 -0
  45. package/dist/server/github-gist.d.ts +11 -0
  46. package/dist/server/github-gist.js +68 -0
  47. package/dist/server/github.d.ts +7 -0
  48. package/dist/server/github.js +65 -0
  49. package/dist/server/index.d.ts +70 -0
  50. package/dist/server/index.js +862 -0
  51. package/dist/server/link.d.ts +64 -0
  52. package/dist/server/link.js +458 -0
  53. package/dist/server/openpgp.d.ts +49 -0
  54. package/dist/server/openpgp.js +158 -0
  55. package/dist/server/pgp.d.ts +29 -0
  56. package/dist/server/pgp.js +198 -0
  57. package/dist/server/public-fetch.d.ts +23 -0
  58. package/dist/server/public-fetch.js +62 -0
  59. package/dist/server/resend.d.ts +13 -0
  60. package/dist/server/resend.js +37 -0
  61. package/dist/server/service.d.ts +311 -0
  62. package/dist/server/service.js +1178 -0
  63. package/dist/server/signing.d.ts +16 -0
  64. package/dist/server/signing.js +56 -0
  65. package/dist/server/style.d.ts +17 -0
  66. package/dist/server/style.js +244 -0
  67. package/dist/server/wkd.d.ts +7 -0
  68. package/dist/server/wkd.js +65 -0
  69. package/dist/server/youtube.d.ts +11 -0
  70. package/dist/server/youtube.js +99 -0
  71. package/dist/site/index.d.ts +139 -0
  72. package/dist/site/index.js +239 -0
  73. package/dist/verily.js +11907 -0
  74. package/dist/version.d.ts +10 -0
  75. package/dist/version.js +10 -0
  76. package/docs/licenses/gpl-3.0.txt +232 -0
  77. package/docs/licenses/octicons.txt +21 -0
  78. package/docs/licenses/openpgp.txt +172 -0
  79. package/docs/licenses/rubik.txt +93 -0
  80. package/docs/licenses/simple-icons.txt +8 -0
  81. package/package.json +86 -0
@@ -0,0 +1,454 @@
1
+ export type Visibility = 'public' | 'unlisted';
2
+ /**
3
+ * `unconfirmed` is a published proof that has not been read lately. It is not disproved,
4
+ * and turns back to `verified` the moment the proof reads again. `expired` is an approval
5
+ * that ran out, which only the holder renewing it undoes.
6
+ */
7
+ export type Status = 'verified' | 'unconfirmed' | 'expired' | 'revoked';
8
+ /** What a site links: one of its accounts, a single page, or the site itself. */
9
+ export type LocalKind = 'account' | 'page' | 'site';
10
+ /** id is private and must never be reassigned. reference is durable and public-safe. */
11
+ export interface LocalAccount {
12
+ id: string;
13
+ /** Absent means the site did not say. Renderers must not assume an account. */
14
+ kind?: LocalKind;
15
+ label: string;
16
+ reference: string;
17
+ /** Canonical URL for the linked subject: a profile, a page, or the site root. */
18
+ profileUrl?: string;
19
+ /**
20
+ * The site this subject belongs to, where one instance serves several. Absent means the
21
+ * instance's own `siteName`. Every page and record describing the subject names this.
22
+ */
23
+ siteName?: string;
24
+ }
25
+ export interface ExternalAccount {
26
+ id: string;
27
+ /**
28
+ * What the other side is. Absent means an account, which is what a provider issues and
29
+ * can reassign. A key is not an account: nobody issued it and nobody can hand it over.
30
+ * A page is an address that was read and nothing more: a method that only fetches a
31
+ * document learns what it says, never whose account the address is. A mailbox is an
32
+ * account in all but how it is written: its domain issued it and can hand it to somebody
33
+ * else, but its address is already its whole name and it has no profile to link to.
34
+ */
35
+ kind?: 'account' | 'key' | 'page' | 'mailbox';
36
+ handle: string;
37
+ /** Where a reader goes to see the subject. For a mailbox, its `mailto:` address. */
38
+ profileUrl: string;
39
+ }
40
+ /**
41
+ * How control of one side was shown. A provider is who an account belongs to; a method is
42
+ * how control of it was demonstrated, and the two multiply rather than enumerate: one
43
+ * provider may support several methods and one method spans providers.
44
+ *
45
+ * `declared` is the site asserting a subject from its own records. That is not a weaker
46
+ * form of the others: a site is the only authority on its own namespace, so no external
47
+ * source could improve on it. A sign-in and a mailed code happen once, between the holder
48
+ * and this backend. The rest publish an artifact a reader can fetch.
49
+ */
50
+ export type Method = 'declared' | 'oauth' | 'gist' | 'backlink' | 'signature' | 'code';
51
+ /** Who vouches for one side: this backend from its own records, or the account's provider. */
52
+ export type Attester = 'backend' | 'provider';
53
+ /**
54
+ * How one side of a link was established. `artifactUrl` is present only when the method
55
+ * leaves a public proof, which is what lets a reader check the claim without trusting
56
+ * this backend, and lets another installation reverify it independently.
57
+ */
58
+ export interface Attestation {
59
+ by: Attester;
60
+ method: Method;
61
+ /** Public location of the proof. Never a sharing link or any other secret. */
62
+ artifactUrl?: string;
63
+ /**
64
+ * True when this backend serves the proof itself rather than reading it somewhere else.
65
+ * A hosted proof cannot go missing behind our back, so it never needs reconfirming.
66
+ */
67
+ hosted?: boolean;
68
+ /** What a reader should expect to find at artifactUrl, such as a challenge token. */
69
+ expect?: string;
70
+ /**
71
+ * When this side was last confirmed. For `declared` and `oauth` that is the moment it
72
+ * was established; artifact methods drift out of date and are reconfirmed on a schedule.
73
+ */
74
+ confirmedAt: number;
75
+ }
76
+ /** Each side of a link is attested separately, by different parties under different methods. */
77
+ export interface Attestations {
78
+ local: Attestation;
79
+ /**
80
+ * Every method the external account has been shown by, in the order each was first
81
+ * used, at most one per method. The first is the main one for the record's life, and a
82
+ * record is judged by it; the rest are additional and never replace it.
83
+ */
84
+ external: [Attestation, ...Attestation[]];
85
+ }
86
+ export interface Connection {
87
+ id: string;
88
+ local: LocalAccount;
89
+ external: ExternalAccount;
90
+ provider: string;
91
+ visibility: Visibility;
92
+ visibilityApprovedAt: number;
93
+ authenticatedAt: number;
94
+ approvedAt: number;
95
+ /**
96
+ * When the record was first made. A renewal moves every other time on the record forward
97
+ * and never this one, which is what says the order a subject's accounts were connected
98
+ * in. Absent on records written before it was kept.
99
+ */
100
+ connectedAt?: number;
101
+ expiresAt: number;
102
+ revokedAt?: number;
103
+ /** Who removed it: the local holder, the external holder, or a withdrawal the provider read. */
104
+ revocationReason?: 'local' | 'external' | 'withdrawn';
105
+ /** Absent on records written before methods were recorded; evidence infers those. */
106
+ attestations?: Attestations;
107
+ /** The proof itself, for a method whose artifact this backend publishes rather than reads. */
108
+ proof?: string;
109
+ }
110
+ export interface Flow {
111
+ id: string;
112
+ stateHash: string;
113
+ bindingHash: string;
114
+ verifier?: string;
115
+ kind: 'connect' | 'renew' | 'revoke' | 'visibility' | 'share-revoke';
116
+ local?: LocalAccount;
117
+ connectionId?: string;
118
+ phase: 'pending' | 'exchanging' | 'approval' | 'complete' | 'cancelled' | 'failed';
119
+ expiresAt: number;
120
+ external?: ExternalAccount;
121
+ authenticatedAt?: number;
122
+ resultId?: string;
123
+ /** Why a failed flow failed, when a provider said so in words meant for the holder. */
124
+ reason?: string;
125
+ /**
126
+ * The string an artifact must contain. Public by design: the holder publishes it. It is
127
+ * unguessable and per-flow, so an artifact made for one flow cannot complete another.
128
+ */
129
+ expect?: string;
130
+ /** What the holder handed back: an address to read, or the proof itself. */
131
+ artifact?: string;
132
+ /**
133
+ * A message on its way to the address the holder named, carrying a link to press and a
134
+ * code to type. The account is only a claim until one of them comes back, which is why
135
+ * it is kept here and not in `external`. Never public: either one is the whole proof, so
136
+ * only their hashes are stored and none of this is reported.
137
+ */
138
+ sent?: {
139
+ account: ExternalAccount;
140
+ codeHash: string;
141
+ linkHash: string;
142
+ attempts: number;
143
+ };
144
+ /**
145
+ * Which configured method the flow runs: the provider's id and its method. Absent on
146
+ * flows written by a backend that had only one, which is the first one configured.
147
+ */
148
+ provider?: string;
149
+ method?: Method;
150
+ /**
151
+ * What the adopting application attached when the flow was created, such as which site
152
+ * sent the holder here. Never set on removal from the external side, which is started by
153
+ * nobody local whatever the browser is signed into.
154
+ */
155
+ context?: Record<string, string>;
156
+ /** How the flow ended, written in the transaction that ended it and never changed after. */
157
+ result?: FlowResult;
158
+ }
159
+ /**
160
+ * The end of a flow as it stood at that moment. The connection may change later, so anything
161
+ * reporting the result reads it from here and never from the connection.
162
+ */
163
+ export interface FlowResult {
164
+ kind: Flow['kind'];
165
+ phase: 'complete' | 'cancelled' | 'failed';
166
+ /** The connection the flow made or acted on, where there is one. */
167
+ connectionId?: string;
168
+ /** That connection's visibility when the flow ended. */
169
+ visibility?: Visibility;
170
+ finishedAt: number;
171
+ }
172
+ export interface Share {
173
+ connectionId: string;
174
+ tokenHash: string;
175
+ createdAt: number;
176
+ expiresAt: number;
177
+ revokedAt?: number;
178
+ }
179
+ export interface Audit {
180
+ id: string;
181
+ connectionId?: string;
182
+ flowId?: string;
183
+ action: string;
184
+ actor: 'local' | 'external';
185
+ at: number;
186
+ visibility?: Visibility;
187
+ }
188
+ /**
189
+ * How many times something has been done in the window that ends at `expiresAt`. Past
190
+ * that the count is spent and the next one starts a window of its own.
191
+ */
192
+ export interface Limit {
193
+ id: string;
194
+ count: number;
195
+ expiresAt: number;
196
+ }
197
+ export interface Records {
198
+ connections: Connection;
199
+ flows: Flow;
200
+ shares: Share;
201
+ audit: Audit;
202
+ limits: Limit;
203
+ }
204
+ export interface Transaction {
205
+ get<K extends keyof Records>(kind: K, id: string): Promise<Records[K] | undefined>;
206
+ put<K extends keyof Records>(kind: K, id: string, value: Records[K]): Promise<void>;
207
+ delete(kind: keyof Records, id: string): Promise<void>;
208
+ list<K extends keyof Records>(kind: K): Promise<Records[K][]>;
209
+ }
210
+ /** Must serialize concurrent transactions and roll back all writes on rejection. */
211
+ export interface Storage {
212
+ transaction<T>(work: (tx: Transaction) => Promise<T>): Promise<T>;
213
+ }
214
+ /**
215
+ * Proves control by sending the holder to the provider and back in one round trip. The
216
+ * provider is the only party that sees the holder's credentials, and nothing is published.
217
+ */
218
+ export interface RedirectProvider {
219
+ id: string;
220
+ name: string;
221
+ /** Absent means oauth, the shape this interface describes. */
222
+ method?: 'oauth';
223
+ authorizationUrl(input: {
224
+ state: string;
225
+ challenge: string;
226
+ redirectUri: string;
227
+ }): string;
228
+ authenticate(input: {
229
+ code: string;
230
+ verifier: string;
231
+ redirectUri: string;
232
+ }): Promise<ExternalAccount>;
233
+ }
234
+ /**
235
+ * Proves control by having the holder publish a given string somewhere only they can write,
236
+ * then reading it back. It is holder-paced rather than one round trip, it needs no
237
+ * registration with the provider, and it leaves a proof a reader can check for themselves.
238
+ */
239
+ export interface ArtifactProvider {
240
+ id: string;
241
+ name: string;
242
+ method: Exclude<Method, 'declared' | 'oauth'>;
243
+ /**
244
+ * What the holder must publish, for a method that does not get to choose it. A backlink
245
+ * says the far side points at this exact subject, so the subject's own address is the
246
+ * whole of what is proved and a per-flow token could not be part of it. Absent means the
247
+ * backend mints an unguessable string instead, which is what a method wants when any
248
+ * fresh artifact will do. Throw when the subject cannot be proved this way at all.
249
+ */
250
+ expect?(local: LocalAccount): string;
251
+ /**
252
+ * Where the proof lives. `location` means the holder publishes it somewhere only they
253
+ * can write and hands back an address, so the address is half of what is proved.
254
+ * `document` means they hand back the proof itself and this backend publishes it,
255
+ * because a signature proves itself and where it was typed proves nothing at all.
256
+ */
257
+ artifact: 'location' | 'document';
258
+ /**
259
+ * Told to the holder verbatim. Must name what they are publishing and where. Returned in
260
+ * pieces so a renderer can set a command as one: a command is copied character for
261
+ * character, and prose that reflows is prose a holder cannot safely copy.
262
+ */
263
+ instructions(expect: string): Instruction[];
264
+ /**
265
+ * Reads what the holder handed back and returns whose it is. Must confirm it contains
266
+ * `expect`. A `location` provider must also refuse any address outside itself, since
267
+ * the holder chooses that address and an unpinned fetch is an open proxy. Throw `Refused`
268
+ * to tell the holder why; any other error is shown only as a failure.
269
+ */
270
+ verify(input: {
271
+ artifact: string;
272
+ expect: string;
273
+ }): Promise<ExternalAccount>;
274
+ /**
275
+ * Whether the holder has since withdrawn the identity itself, wherever such a statement
276
+ * is published. This revokes the connection, so it must be something the holder stated
277
+ * and this provider verified, never something a third party merely asserted.
278
+ */
279
+ withdrawn?(account: ExternalAccount, artifact: string): Promise<boolean>;
280
+ }
281
+ /**
282
+ * Proves control by sending a message somewhere only the holder can read and having them
283
+ * answer it: by pressing the link it carries, or by reading its code back. It is
284
+ * holder-paced like an artifact, but nothing is published: the proof is that the message
285
+ * arrived, so like a sign-in it happened once and there is nothing for a reader to fetch
286
+ * or for a recheck to read again.
287
+ *
288
+ * The backend mints the link and the code, counts the guesses and never hands the
289
+ * provider anything else to say, so a provider cannot be made to carry a stranger's words
290
+ * to an address a stranger chose.
291
+ */
292
+ export interface CodeProvider {
293
+ id: string;
294
+ name: string;
295
+ method: 'code';
296
+ /** What the holder is asked for, such as "Your email address". */
297
+ field: string;
298
+ /** The kind of address, which says what a browser offers to fill in for it. */
299
+ input: 'email' | 'tel';
300
+ /**
301
+ * Whose account an address names, in the one spelling this provider keeps for it.
302
+ * Called before anything is sent. Throw `Refused` for an address it will not send to.
303
+ */
304
+ account(address: string): ExternalAccount;
305
+ /** Sends the message. Rejecting fails the flow, and only a `Refused` reason is shown. */
306
+ deliver(input: {
307
+ account: ExternalAccount;
308
+ /**
309
+ * A page on this backend that confirms the address once its holder presses the button
310
+ * there. Following the link alone confirms nothing, so a scanner that opens every link
311
+ * in a message cannot answer for the holder. Works from any browser.
312
+ */
313
+ link: string;
314
+ /** The same proof as something to type, into the page that asked. */
315
+ code: string;
316
+ /**
317
+ * Who the message says is asking: the site the holder is linking this account to, or,
318
+ * for a flow anybody may start on somebody else's record, only this installation.
319
+ */
320
+ siteName: string;
321
+ /** When both stop working. */
322
+ expiresAt: number;
323
+ }): Promise<void>;
324
+ }
325
+ /**
326
+ * A proof turned down for a reason the holder can act on. Its message is shown to them, so
327
+ * it names what was wrong with what they handed over and nothing about the backend.
328
+ */
329
+ export declare class Refused extends Error {
330
+ }
331
+ /**
332
+ * A piece of what the holder is told: a paragraph, a paragraph with links in it, or
333
+ * something they run or publish as is.
334
+ */
335
+ export type Instruction = string | {
336
+ code: string;
337
+ } | Inline[];
338
+ /**
339
+ * A piece of a paragraph: text, or text that links somewhere the holder goes to do what
340
+ * the paragraph says. Renderers link only http(s) addresses and open them in a new tab.
341
+ */
342
+ export type Inline = string | {
343
+ text: string;
344
+ href: string;
345
+ };
346
+ export type Provider = RedirectProvider | ArtifactProvider | CodeProvider;
347
+ /** Only an artifact provider is holder-paced, and only it needs a url handed back. */
348
+ export declare function isArtifactProvider(provider: Provider): provider is ArtifactProvider;
349
+ /** The one shape that sends the holder to the provider's own site and takes them back. */
350
+ export declare function isRedirectProvider(provider: Provider): provider is RedirectProvider;
351
+ /** Only a code provider sends the holder something to read back. */
352
+ export declare function isCodeProvider(provider: Provider): provider is CodeProvider;
353
+ /** How a provider shows control. A redirect provider that names none is oauth. */
354
+ export declare function providerMethod(provider: Provider): Method;
355
+ export interface Evidence {
356
+ id: string;
357
+ /** The site's name is `siteName` below, so the subject does not carry a second copy. */
358
+ local: Omit<LocalAccount, 'id' | 'siteName'>;
359
+ external: ExternalAccount;
360
+ provider: string;
361
+ /** Display name from the provider implementation. */
362
+ providerName: string;
363
+ /**
364
+ * How each side was established, so a reader can weigh them separately instead of
365
+ * reading one undifferentiated "verified".
366
+ */
367
+ attestations: Attestations;
368
+ siteName: string;
369
+ verifierName: string;
370
+ visibility: Visibility;
371
+ status: Status;
372
+ /**
373
+ * When the record was first made, which no renewal changes. A subject's accounts are
374
+ * shown in this order, earliest first. For a record older than this field it is the
375
+ * earliest time the record still carries.
376
+ */
377
+ connectedAt: number;
378
+ authenticatedAt: number;
379
+ approvedAt: number;
380
+ visibilityApprovedAt: number;
381
+ expiresAt: number;
382
+ revokedAt?: number;
383
+ evidenceUrl: string;
384
+ /**
385
+ * Where this record can be had signed, on a verifier that signs. Only a public record
386
+ * that stands has a signed form: once saved, a signed record cannot be taken back.
387
+ */
388
+ signedUrl?: string;
389
+ }
390
+ export * from './signed.js';
391
+ /**
392
+ * How long a published proof stays good without being read again. An artifact method is
393
+ * only true while the artifact is still there, and the holder can delete it without
394
+ * telling anyone, so a confirmation is a heartbeat rather than a permanent fact.
395
+ */
396
+ export declare const freshnessMs: number;
397
+ /**
398
+ * `freshness` bounds how stale a published proof may be before it stops counting. A
399
+ * backend that never rechecks must leave it unbounded, since an unread proof going stale
400
+ * is a statement about the recheck, and claiming one that never ran would be a lie.
401
+ */
402
+ export declare function status(connection: Connection, now: number, freshness?: number): Status;
403
+ /**
404
+ * Whether one method still counts. A sign-in happened once and stays happened. A proof
405
+ * that has not been read lately is unconfirmed rather than disproved, which is why this
406
+ * reverses the moment it reads again.
407
+ */
408
+ export declare function fresh(attestation: Attestation, now: number, freshness?: number): boolean;
409
+ /**
410
+ * The word for a record's state. Evidence can be read after it was issued, so an approval
411
+ * that has run out since reads as expired whatever status it was issued with.
412
+ */
413
+ export declare function statusLabel(evidence: Pick<Evidence, 'status' | 'expiresAt'>, now: number): string;
414
+ /**
415
+ * What to call a link's local side, and what to write as its value. When the site itself is
416
+ * what was linked there is no subject on it to name, so the site is the value rather than a
417
+ * label above some other thing. An absent kind means the site did not say, so nothing is
418
+ * assumed about one. A subject that names its own site is described by that name, and
419
+ * `siteName` is only the fallback.
420
+ */
421
+ export declare function localSide(local: Pick<LocalAccount, 'kind' | 'label' | 'siteName'>, fallback: string): {
422
+ heading: string;
423
+ value: string;
424
+ };
425
+ /**
426
+ * How to write an external subject's name. The @ that marks a handle is a claim that there
427
+ * is an account behind it, issued by somebody who could also take it away. A key has no
428
+ * account and no handle: it is named by its own fingerprint, so it is written as it is. A
429
+ * page is named by its address for the same reason, since fetching one says where it is
430
+ * and never who holds it. A mailbox's address already carries its own @.
431
+ */
432
+ export declare function externalName(external: ExternalAccount): string;
433
+ /**
434
+ * How to describe one side's proof in plain words. It names the attester and the method
435
+ * together and does not rank them: whether a given proof is convincing is the reader's
436
+ * judgement, which is the reason for publishing it rather than a verdict about it.
437
+ *
438
+ * An unrecognised method returns nothing, so a renderer omits the line instead of
439
+ * describing a proof it does not understand.
440
+ */
441
+ export declare function attestationLabel(method: string, names: {
442
+ site: string;
443
+ provider: string;
444
+ }): string | undefined;
445
+ /**
446
+ * Where a subject's name may link, or nothing where it has no page of its own. A mailbox's
447
+ * address is `mailto:`, which opens a message to it and shows a reader nothing.
448
+ */
449
+ export declare function externalLink(external: ExternalAccount): string | undefined;
450
+ /**
451
+ * The hover text on a proof link: where it leads, then when it was last read there. A
452
+ * proof this backend serves itself is never reread, so it gives no such time.
453
+ */
454
+ export declare function proofTitle(attestation: Attestation, when: (time: number) => string): string;
@@ -0,0 +1,128 @@
1
+ /**
2
+ * A proof turned down for a reason the holder can act on. Its message is shown to them, so
3
+ * it names what was wrong with what they handed over and nothing about the backend.
4
+ */
5
+ export class Refused extends Error {
6
+ }
7
+ /** Only an artifact provider is holder-paced, and only it needs a url handed back. */
8
+ export function isArtifactProvider(provider) {
9
+ return 'verify' in provider;
10
+ }
11
+ /** The one shape that sends the holder to the provider's own site and takes them back. */
12
+ export function isRedirectProvider(provider) {
13
+ return 'authenticate' in provider;
14
+ }
15
+ /** Only a code provider sends the holder something to read back. */
16
+ export function isCodeProvider(provider) {
17
+ return 'deliver' in provider;
18
+ }
19
+ /** How a provider shows control. A redirect provider that names none is oauth. */
20
+ export function providerMethod(provider) {
21
+ return provider.method ?? 'oauth';
22
+ }
23
+ export * from './signed.js';
24
+ /**
25
+ * How long a published proof stays good without being read again. An artifact method is
26
+ * only true while the artifact is still there, and the holder can delete it without
27
+ * telling anyone, so a confirmation is a heartbeat rather than a permanent fact.
28
+ */
29
+ export const freshnessMs = 7 * 86400000;
30
+ /**
31
+ * `freshness` bounds how stale a published proof may be before it stops counting. A
32
+ * backend that never rechecks must leave it unbounded, since an unread proof going stale
33
+ * is a statement about the recheck, and claiming one that never ran would be a lie.
34
+ */
35
+ export function status(connection, now, freshness = freshnessMs) {
36
+ if (connection.revokedAt !== undefined)
37
+ return 'revoked';
38
+ if (now >= connection.expiresAt)
39
+ return 'expired';
40
+ const main = connection.attestations?.external[0];
41
+ if (main && !fresh(main, now, freshness))
42
+ return 'unconfirmed';
43
+ return 'verified';
44
+ }
45
+ /**
46
+ * Whether one method still counts. A sign-in happened once and stays happened. A proof
47
+ * that has not been read lately is unconfirmed rather than disproved, which is why this
48
+ * reverses the moment it reads again.
49
+ */
50
+ export function fresh(attestation, now, freshness = freshnessMs) {
51
+ return (!attestation.artifactUrl ||
52
+ attestation.hosted === true ||
53
+ freshness === Infinity ||
54
+ now < attestation.confirmedAt + freshness);
55
+ }
56
+ /**
57
+ * The word for a record's state. Evidence can be read after it was issued, so an approval
58
+ * that has run out since reads as expired whatever status it was issued with.
59
+ */
60
+ export function statusLabel(evidence, now) {
61
+ if (evidence.status === 'revoked')
62
+ return 'Revoked';
63
+ if (evidence.status === 'expired' || evidence.expiresAt <= now)
64
+ return 'Expired';
65
+ return evidence.status === 'unconfirmed' ? 'Unconfirmed' : 'Verified';
66
+ }
67
+ /**
68
+ * What to call a link's local side, and what to write as its value. When the site itself is
69
+ * what was linked there is no subject on it to name, so the site is the value rather than a
70
+ * label above some other thing. An absent kind means the site did not say, so nothing is
71
+ * assumed about one. A subject that names its own site is described by that name, and
72
+ * `siteName` is only the fallback.
73
+ */
74
+ export function localSide(local, fallback) {
75
+ const siteName = local.siteName ?? fallback;
76
+ if (local.kind === 'site')
77
+ return { heading: 'Website', value: siteName };
78
+ const heading = {
79
+ account: `Account on ${siteName}`,
80
+ page: `Page on ${siteName}`,
81
+ };
82
+ return { heading: heading[local.kind ?? ''] ?? siteName, value: local.label };
83
+ }
84
+ /**
85
+ * How to write an external subject's name. The @ that marks a handle is a claim that there
86
+ * is an account behind it, issued by somebody who could also take it away. A key has no
87
+ * account and no handle: it is named by its own fingerprint, so it is written as it is. A
88
+ * page is named by its address for the same reason, since fetching one says where it is
89
+ * and never who holds it. A mailbox's address already carries its own @.
90
+ */
91
+ export function externalName(external) {
92
+ return external.kind === undefined || external.kind === 'account'
93
+ ? `@${external.handle.replace(/^@/, '')}`
94
+ : external.handle;
95
+ }
96
+ /**
97
+ * How to describe one side's proof in plain words. It names the attester and the method
98
+ * together and does not rank them: whether a given proof is convincing is the reader's
99
+ * judgement, which is the reason for publishing it rather than a verdict about it.
100
+ *
101
+ * An unrecognised method returns nothing, so a renderer omits the line instead of
102
+ * describing a proof it does not understand.
103
+ */
104
+ export function attestationLabel(method, names) {
105
+ return {
106
+ declared: `Stated by ${names.site}`,
107
+ oauth: `Signed in with ${names.provider}`,
108
+ gist: `Published a proof on ${names.provider}`,
109
+ backlink: `Linked back to ${names.site}`,
110
+ signature: 'Proved with a signature',
111
+ code: 'Entered a code sent to this address',
112
+ }[method];
113
+ }
114
+ /**
115
+ * Where a subject's name may link, or nothing where it has no page of its own. A mailbox's
116
+ * address is `mailto:`, which opens a message to it and shows a reader nothing.
117
+ */
118
+ export function externalLink(external) {
119
+ return external.kind === 'mailbox' ? undefined : external.profileUrl;
120
+ }
121
+ /**
122
+ * The hover text on a proof link: where it leads, then when it was last read there. A
123
+ * proof this backend serves itself is never reread, so it gives no such time.
124
+ */
125
+ export function proofTitle(attestation, when) {
126
+ const where = `View the proof at ${new URL(attestation.artifactUrl).host}`;
127
+ return attestation.hosted ? where : `${where} | Last checked ${when(attestation.confirmedAt)}`;
128
+ }
@@ -0,0 +1,49 @@
1
+ import type { Evidence } from './index.js';
2
+ /**
3
+ * A verifier's public key, as it publishes it: an OpenPGP key, named by its fingerprint.
4
+ * `alg` says how a record under it is read, so another scheme can sit beside this one.
5
+ */
6
+ export interface VerifierKey {
7
+ /** The key's fingerprint, in the upper case it is read aloud in. */
8
+ id: string;
9
+ alg: 'OpenPGP';
10
+ /** The armored public key, as `gpg --import` reads it. */
11
+ publicKey: string;
12
+ }
13
+ /**
14
+ * What a signature covers: the record as it stood at `issuedAt`. Whether it still stands
15
+ * is a live fact no signature holds, so its status is left out and `evidenceUrl` is where
16
+ * to ask.
17
+ */
18
+ export type SignedDocument = Omit<Evidence, 'status' | 'revokedAt' | 'signedUrl'> & {
19
+ type: 'verily-evidence';
20
+ version: 1;
21
+ issuedAt: number;
22
+ };
23
+ /**
24
+ * A record that can be checked away from the verifier that served it: the document as
25
+ * text under an OpenPGP cleartext signature, which `gpg --verify` reads as it is.
26
+ */
27
+ export type SignedEvidence = string;
28
+ export interface Signer {
29
+ key: VerifierKey;
30
+ sign(document: SignedDocument): Promise<SignedEvidence>;
31
+ }
32
+ /**
33
+ * The fingerprint of the key a signed record says signed it, checked or not: which key to
34
+ * look for, and no reason by itself to believe anything.
35
+ */
36
+ export declare function signedBy(signed: unknown): Promise<string | undefined>;
37
+ /**
38
+ * The document a signed record carries, if one of the keys given signed it, and nothing
39
+ * otherwise. The keys are the caller's to choose: a record is only as good as the reason
40
+ * to believe each key is the verifier's. It says what stood when it was signed, never
41
+ * that it stands now.
42
+ *
43
+ * Whether a signature counts is OpenPGP.js's to say, by the format's own rules: a key is
44
+ * judged as it stood when the record was signed, so one that has since run out, or been
45
+ * retired, still answers for what it signed before, and one revoked as compromised signs
46
+ * nothing at all. Only a record signed by the listed key itself is taken, not by a subkey
47
+ * of it, so the fingerprint a record names is the one a list holds.
48
+ */
49
+ export declare function verifySigned(signed: unknown, keys: VerifierKey[]): Promise<SignedDocument | undefined>;