@geekity/cms 0.12.0 → 0.14.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 (278) hide show
  1. package/README.md +188 -9
  2. package/admin/pages/account/login.njk +3 -0
  3. package/admin/pages/documents/editor.njk +20 -0
  4. package/admin/pages/indieauth/consent.njk +51 -0
  5. package/admin/pages/indieauth/refused.njk +11 -0
  6. package/admin/pages/settings/general.njk +12 -2
  7. package/admin/pages/settings/reading.njk +93 -0
  8. package/admin/pages/users/apps.njk +74 -0
  9. package/admin/pages/users/edit.njk +12 -0
  10. package/admin/static/admin.css +27 -0
  11. package/dist/admin/accounts.d.ts +3 -0
  12. package/dist/admin/accounts.d.ts.map +1 -1
  13. package/dist/admin/accounts.js +5 -0
  14. package/dist/admin/accounts.js.map +1 -1
  15. package/dist/admin/documents.d.ts +4 -0
  16. package/dist/admin/documents.d.ts.map +1 -1
  17. package/dist/admin/documents.js +45 -5
  18. package/dist/admin/documents.js.map +1 -1
  19. package/dist/admin/federation.js +2 -2
  20. package/dist/admin/federation.js.map +1 -1
  21. package/dist/admin/headers.d.ts +9 -2
  22. package/dist/admin/headers.d.ts.map +1 -1
  23. package/dist/admin/headers.js +16 -7
  24. package/dist/admin/headers.js.map +1 -1
  25. package/dist/admin/menu.d.ts.map +1 -1
  26. package/dist/admin/menu.js +2 -1
  27. package/dist/admin/menu.js.map +1 -1
  28. package/dist/admin/routes.d.ts.map +1 -1
  29. package/dist/admin/routes.js +48 -5
  30. package/dist/admin/routes.js.map +1 -1
  31. package/dist/admin/settings-general.d.ts.map +1 -1
  32. package/dist/admin/settings-general.js +28 -3
  33. package/dist/admin/settings-general.js.map +1 -1
  34. package/dist/admin/settings-reading.d.ts.map +1 -1
  35. package/dist/admin/settings-reading.js +15 -1
  36. package/dist/admin/settings-reading.js.map +1 -1
  37. package/dist/admin/settings.d.ts +83 -4
  38. package/dist/admin/settings.d.ts.map +1 -1
  39. package/dist/admin/settings.js +166 -4
  40. package/dist/admin/settings.js.map +1 -1
  41. package/dist/admin/templates.d.ts +3 -0
  42. package/dist/admin/templates.d.ts.map +1 -1
  43. package/dist/admin/templates.js +3 -0
  44. package/dist/admin/templates.js.map +1 -1
  45. package/dist/admin/users.d.ts +8 -0
  46. package/dist/admin/users.d.ts.map +1 -1
  47. package/dist/admin/users.js +9 -0
  48. package/dist/admin/users.js.map +1 -1
  49. package/dist/config.d.ts +34 -0
  50. package/dist/config.d.ts.map +1 -1
  51. package/dist/config.js +1 -0
  52. package/dist/config.js.map +1 -1
  53. package/dist/content/pinned.d.ts +26 -0
  54. package/dist/content/pinned.d.ts.map +1 -0
  55. package/dist/content/pinned.js +42 -0
  56. package/dist/content/pinned.js.map +1 -0
  57. package/dist/content/store.d.ts +7 -0
  58. package/dist/content/store.d.ts.map +1 -1
  59. package/dist/content/store.js +20 -0
  60. package/dist/content/store.js.map +1 -1
  61. package/dist/env.d.ts +21 -0
  62. package/dist/env.d.ts.map +1 -1
  63. package/dist/federation/actor.d.ts +15 -0
  64. package/dist/federation/actor.d.ts.map +1 -1
  65. package/dist/federation/actor.js +94 -3
  66. package/dist/federation/actor.js.map +1 -1
  67. package/dist/federation/article.d.ts +13 -1
  68. package/dist/federation/article.d.ts.map +1 -1
  69. package/dist/federation/article.js +45 -6
  70. package/dist/federation/article.js.map +1 -1
  71. package/dist/federation/delivery.d.ts.map +1 -1
  72. package/dist/federation/delivery.js +36 -4
  73. package/dist/federation/delivery.js.map +1 -1
  74. package/dist/federation/federation.d.ts.map +1 -1
  75. package/dist/federation/federation.js +24 -2
  76. package/dist/federation/federation.js.map +1 -1
  77. package/dist/federation/mount.js +2 -2
  78. package/dist/federation/mount.js.map +1 -1
  79. package/dist/federation/paths.d.ts +30 -0
  80. package/dist/federation/paths.d.ts.map +1 -1
  81. package/dist/federation/paths.js +36 -0
  82. package/dist/federation/paths.js.map +1 -1
  83. package/dist/images/icons.d.ts +44 -25
  84. package/dist/images/icons.d.ts.map +1 -1
  85. package/dist/images/icons.js +205 -60
  86. package/dist/images/icons.js.map +1 -1
  87. package/dist/images/index.d.ts +2 -2
  88. package/dist/images/index.d.ts.map +1 -1
  89. package/dist/images/index.js +1 -1
  90. package/dist/images/index.js.map +1 -1
  91. package/dist/images/markup.d.ts +14 -0
  92. package/dist/images/markup.d.ts.map +1 -1
  93. package/dist/images/markup.js +16 -11
  94. package/dist/images/markup.js.map +1 -1
  95. package/dist/images/variants.d.ts +11 -0
  96. package/dist/images/variants.d.ts.map +1 -1
  97. package/dist/images/variants.js +24 -4
  98. package/dist/images/variants.js.map +1 -1
  99. package/dist/index.d.ts +15 -5
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +33 -2
  102. package/dist/index.js.map +1 -1
  103. package/dist/indexnow.d.ts +34 -0
  104. package/dist/indexnow.d.ts.map +1 -0
  105. package/dist/indexnow.js +141 -0
  106. package/dist/indexnow.js.map +1 -0
  107. package/dist/indieauth/bearer.d.ts +39 -0
  108. package/dist/indieauth/bearer.d.ts.map +1 -0
  109. package/dist/indieauth/bearer.js +63 -0
  110. package/dist/indieauth/bearer.js.map +1 -0
  111. package/dist/indieauth/client.d.ts +61 -0
  112. package/dist/indieauth/client.d.ts.map +1 -0
  113. package/dist/indieauth/client.js +139 -0
  114. package/dist/indieauth/client.js.map +1 -0
  115. package/dist/indieauth/connected-apps.d.ts +23 -0
  116. package/dist/indieauth/connected-apps.d.ts.map +1 -0
  117. package/dist/indieauth/connected-apps.js +73 -0
  118. package/dist/indieauth/connected-apps.js.map +1 -0
  119. package/dist/indieauth/consent.d.ts +28 -0
  120. package/dist/indieauth/consent.d.ts.map +1 -0
  121. package/dist/indieauth/consent.js +185 -0
  122. package/dist/indieauth/consent.js.map +1 -0
  123. package/dist/indieauth/discovery.d.ts +87 -0
  124. package/dist/indieauth/discovery.d.ts.map +1 -0
  125. package/dist/indieauth/discovery.js +127 -0
  126. package/dist/indieauth/discovery.js.map +1 -0
  127. package/dist/indieauth/grants.d.ts +57 -0
  128. package/dist/indieauth/grants.d.ts.map +1 -0
  129. package/dist/indieauth/grants.js +35 -0
  130. package/dist/indieauth/grants.js.map +1 -0
  131. package/dist/indieauth/identity.d.ts +36 -0
  132. package/dist/indieauth/identity.d.ts.map +1 -0
  133. package/dist/indieauth/identity.js +77 -0
  134. package/dist/indieauth/identity.js.map +1 -0
  135. package/dist/indieauth/redeem.d.ts +47 -0
  136. package/dist/indieauth/redeem.d.ts.map +1 -0
  137. package/dist/indieauth/redeem.js +69 -0
  138. package/dist/indieauth/redeem.js.map +1 -0
  139. package/dist/indieauth/request.d.ts +47 -0
  140. package/dist/indieauth/request.d.ts.map +1 -0
  141. package/dist/indieauth/request.js +104 -0
  142. package/dist/indieauth/request.js.map +1 -0
  143. package/dist/indieauth/token.d.ts +19 -0
  144. package/dist/indieauth/token.d.ts.map +1 -0
  145. package/dist/indieauth/token.js +110 -0
  146. package/dist/indieauth/token.js.map +1 -0
  147. package/dist/indieauth/tokens.d.ts +131 -0
  148. package/dist/indieauth/tokens.d.ts.map +1 -0
  149. package/dist/indieauth/tokens.js +205 -0
  150. package/dist/indieauth/tokens.js.map +1 -0
  151. package/dist/web/admin-bar.d.ts +3 -1
  152. package/dist/web/admin-bar.d.ts.map +1 -1
  153. package/dist/web/admin-bar.js +5 -1
  154. package/dist/web/admin-bar.js.map +1 -1
  155. package/dist/web/archive.d.ts +1 -1
  156. package/dist/web/archive.d.ts.map +1 -1
  157. package/dist/web/archive.js +3 -2
  158. package/dist/web/archive.js.map +1 -1
  159. package/dist/web/authors.d.ts +19 -0
  160. package/dist/web/authors.d.ts.map +1 -1
  161. package/dist/web/authors.js +25 -0
  162. package/dist/web/authors.js.map +1 -1
  163. package/dist/web/context.d.ts +25 -0
  164. package/dist/web/context.d.ts.map +1 -1
  165. package/dist/web/context.js +18 -0
  166. package/dist/web/context.js.map +1 -1
  167. package/dist/web/feed-atom.d.ts.map +1 -1
  168. package/dist/web/feed-atom.js +5 -2
  169. package/dist/web/feed-atom.js.map +1 -1
  170. package/dist/web/feed-item.d.ts +24 -6
  171. package/dist/web/feed-item.d.ts.map +1 -1
  172. package/dist/web/feed-item.js +16 -8
  173. package/dist/web/feed-item.js.map +1 -1
  174. package/dist/web/feed-json.d.ts +6 -2
  175. package/dist/web/feed-json.d.ts.map +1 -1
  176. package/dist/web/feed-json.js +6 -2
  177. package/dist/web/feed-json.js.map +1 -1
  178. package/dist/web/feed-rss.d.ts.map +1 -1
  179. package/dist/web/feed-rss.js +17 -4
  180. package/dist/web/feed-rss.js.map +1 -1
  181. package/dist/web/feed-source.d.ts +31 -0
  182. package/dist/web/feed-source.d.ts.map +1 -1
  183. package/dist/web/feed-source.js +31 -0
  184. package/dist/web/feed-source.js.map +1 -1
  185. package/dist/web/feed-xml.d.ts +2 -0
  186. package/dist/web/feed-xml.d.ts.map +1 -1
  187. package/dist/web/feed-xml.js +2 -0
  188. package/dist/web/feed-xml.js.map +1 -1
  189. package/dist/web/feeds.d.ts.map +1 -1
  190. package/dist/web/feeds.js +15 -3
  191. package/dist/web/feeds.js.map +1 -1
  192. package/dist/web/index.d.ts +4 -2
  193. package/dist/web/index.d.ts.map +1 -1
  194. package/dist/web/index.js +3 -2
  195. package/dist/web/index.js.map +1 -1
  196. package/dist/web/indexnow.d.ts +9 -0
  197. package/dist/web/indexnow.d.ts.map +1 -0
  198. package/dist/web/indexnow.js +27 -0
  199. package/dist/web/indexnow.js.map +1 -0
  200. package/dist/web/llms.d.ts +58 -0
  201. package/dist/web/llms.d.ts.map +1 -0
  202. package/dist/web/llms.js +91 -0
  203. package/dist/web/llms.js.map +1 -0
  204. package/dist/web/locale.d.ts +41 -0
  205. package/dist/web/locale.d.ts.map +1 -0
  206. package/dist/web/locale.js +56 -0
  207. package/dist/web/locale.js.map +1 -0
  208. package/dist/web/manifest.d.ts +50 -0
  209. package/dist/web/manifest.d.ts.map +1 -0
  210. package/dist/web/manifest.js +56 -0
  211. package/dist/web/manifest.js.map +1 -0
  212. package/dist/web/negotiate.d.ts.map +1 -1
  213. package/dist/web/negotiate.js +6 -0
  214. package/dist/web/negotiate.js.map +1 -1
  215. package/dist/web/oembed.d.ts +104 -0
  216. package/dist/web/oembed.d.ts.map +1 -0
  217. package/dist/web/oembed.js +290 -0
  218. package/dist/web/oembed.js.map +1 -0
  219. package/dist/web/opensearch.d.ts +35 -0
  220. package/dist/web/opensearch.d.ts.map +1 -0
  221. package/dist/web/opensearch.js +65 -0
  222. package/dist/web/opensearch.js.map +1 -0
  223. package/dist/web/render.d.ts +6 -1
  224. package/dist/web/render.d.ts.map +1 -1
  225. package/dist/web/render.js +40 -10
  226. package/dist/web/render.js.map +1 -1
  227. package/dist/web/robots.d.ts +89 -0
  228. package/dist/web/robots.d.ts.map +1 -0
  229. package/dist/web/robots.js +259 -0
  230. package/dist/web/robots.js.map +1 -0
  231. package/dist/web/routes.d.ts.map +1 -1
  232. package/dist/web/routes.js +179 -8
  233. package/dist/web/routes.js.map +1 -1
  234. package/dist/web/share-image.d.ts +51 -0
  235. package/dist/web/share-image.d.ts.map +1 -0
  236. package/dist/web/share-image.js +43 -0
  237. package/dist/web/share-image.js.map +1 -0
  238. package/dist/web/sitemap.d.ts +0 -26
  239. package/dist/web/sitemap.d.ts.map +1 -1
  240. package/dist/web/sitemap.js +0 -44
  241. package/dist/web/sitemap.js.map +1 -1
  242. package/dist/web/templates.d.ts +16 -9
  243. package/dist/web/templates.d.ts.map +1 -1
  244. package/dist/web/templates.js +88 -55
  245. package/dist/web/templates.js.map +1 -1
  246. package/dist/web/themes.d.ts +33 -0
  247. package/dist/web/themes.d.ts.map +1 -1
  248. package/dist/web/themes.js +57 -0
  249. package/dist/web/themes.js.map +1 -1
  250. package/dist/webmention/discovery.d.ts +4 -2
  251. package/dist/webmention/discovery.d.ts.map +1 -1
  252. package/dist/webmention/discovery.js +14 -8
  253. package/dist/webmention/discovery.js.map +1 -1
  254. package/dist/webmention/fetch-public.d.ts +2 -0
  255. package/dist/webmention/fetch-public.d.ts.map +1 -1
  256. package/dist/webmention/fetch-public.js +1 -1
  257. package/dist/webmention/fetch-public.js.map +1 -1
  258. package/package.json +2 -1
  259. package/templates/site/content/_data/site.json +0 -1
  260. package/themes/default/README.md +174 -43
  261. package/themes/default/layouts/base.njk +116 -17
  262. package/themes/default/layouts/front-page.njk +7 -1
  263. package/themes/default/layouts/home.njk +11 -0
  264. package/themes/default/layouts/page.njk +4 -3
  265. package/themes/default/layouts/post.njk +5 -4
  266. package/themes/default/layouts/search.njk +4 -5
  267. package/themes/default/partials/archive.njk +3 -1
  268. package/themes/default/partials/bio.njk +16 -2
  269. package/themes/default/partials/breadcrumbs.njk +20 -0
  270. package/themes/default/partials/conversation.njk +4 -1
  271. package/themes/default/partials/jsonld.njk +49 -9
  272. package/themes/default/partials/kicker.njk +6 -4
  273. package/themes/default/partials/lang.njk +7 -0
  274. package/themes/default/partials/post-list.njk +5 -3
  275. package/themes/default/partials/reply-context.njk +4 -2
  276. package/themes/default/src/style.css +14 -0
  277. package/themes/default/static/style.css +57 -0
  278. package/themes/default/theme.json +2 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/indieauth/identity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import type { User } from '../admin/accounts.ts';
2
+ import type { AuthorizationCode, ExpiringStore } from './grants.ts';
3
+ /** The OAuth errors a redemption can end in. */
4
+ export type RedemptionError = 'invalid_request' | 'invalid_grant' | 'unsupported_grant_type';
5
+ export type Redemption = {
6
+ readonly ok: true;
7
+ readonly grant: AuthorizationCode;
8
+ } | {
9
+ readonly ok: false;
10
+ readonly error: RedemptionError;
11
+ readonly description: string;
12
+ };
13
+ /** The form fields a client posts to redeem a code, as they arrived. */
14
+ export type RedemptionForm = Readonly<Record<string, string | undefined>>;
15
+ /** A parsed form body as a {@link RedemptionForm}, a file upload read as absent. */
16
+ export declare function redemptionForm(body: Readonly<Record<string, unknown>>): RedemptionForm;
17
+ /**
18
+ * Take the code a client posts and check it against what it was issued for
19
+ * (TASK-159). The token endpoint (TASK-160) redeems codes through this too,
20
+ * so the two cannot disagree about what makes a code good.
21
+ *
22
+ * The code is taken before it is checked, so a try with the wrong verifier,
23
+ * client or redirect spends it: a code somebody is guessing the verifier for
24
+ * is a code that has leaked. `grant_type` may be left out, as clients written
25
+ * to the IndieAuth spec before 2020 do.
26
+ */
27
+ export declare function redeemCode(codes: ExpiringStore<AuthorizationCode>, form: RedemptionForm): Redemption;
28
+ /** The IndieAuth profile URL response: who signed in, and what they let the client see. */
29
+ export interface ProfileResponse {
30
+ readonly me: string;
31
+ readonly profile?: {
32
+ readonly name?: string;
33
+ readonly url?: string;
34
+ readonly photo?: string;
35
+ readonly email?: string;
36
+ };
37
+ }
38
+ /**
39
+ * What profile redemption hands back for `grant`, signed in as `user`. The
40
+ * token endpoint answers it beside the token, for a code or a refresh.
41
+ *
42
+ * The name, url and photo come with the profile scope and the email with the
43
+ * email scope; the IndieAuth spec keeps all four under `profile`. The url is
44
+ * the me, the page that already carries this person's h-card.
45
+ */
46
+ export declare function profileResponse(grant: Pick<AuthorizationCode, 'me' | 'scopes'>, user: User, baseUrl: string): ProfileResponse;
47
+ //# sourceMappingURL=redeem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redeem.d.ts","sourceRoot":"","sources":["../../src/indieauth/redeem.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAEjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEpE,gDAAgD;AAChD,MAAM,MAAM,eAAe,GAAG,iBAAiB,GAAG,eAAe,GAAG,wBAAwB,CAAC;AAE7F,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,iBAAiB,CAAA;CAAE,GACxD;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1F,wEAAwE;AACxE,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;AAE1E,oFAAoF;AACpF,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,cAAc,CAOtF;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,aAAa,CAAC,iBAAiB,CAAC,EACvC,IAAI,EAAE,cAAc,GACnB,UAAU,CA2BZ;AAMD,2FAA2F;AAC3F,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,CAAC,EAAE;QACjB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;CACH;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,IAAI,CAAC,iBAAiB,EAAE,IAAI,GAAG,QAAQ,CAAC,EAC/C,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,GACd,eAAe,CAcjB"}
@@ -0,0 +1,69 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { avatarUrl } from '../federation/actor.js';
3
+ /** A parsed form body as a {@link RedemptionForm}, a file upload read as absent. */
4
+ export function redemptionForm(body) {
5
+ return Object.fromEntries(Object.entries(body).map(([name, value]) => [
6
+ name,
7
+ typeof value === 'string' ? value : undefined,
8
+ ]));
9
+ }
10
+ /**
11
+ * Take the code a client posts and check it against what it was issued for
12
+ * (TASK-159). The token endpoint (TASK-160) redeems codes through this too,
13
+ * so the two cannot disagree about what makes a code good.
14
+ *
15
+ * The code is taken before it is checked, so a try with the wrong verifier,
16
+ * client or redirect spends it: a code somebody is guessing the verifier for
17
+ * is a code that has leaked. `grant_type` may be left out, as clients written
18
+ * to the IndieAuth spec before 2020 do.
19
+ */
20
+ export function redeemCode(codes, form) {
21
+ const grantType = form['grant_type'];
22
+ if (grantType !== undefined && grantType !== 'authorization_code') {
23
+ return refuse('unsupported_grant_type', 'Only authorization_code is supported.');
24
+ }
25
+ const code = form['code'];
26
+ const clientId = form['client_id'];
27
+ const redirectUri = form['redirect_uri'];
28
+ const verifier = form['code_verifier'];
29
+ if (!code || !clientId || !redirectUri || !verifier) {
30
+ return refuse('invalid_request', 'code, client_id, redirect_uri and code_verifier are all required.');
31
+ }
32
+ const grant = codes.take(code);
33
+ if (grant === undefined) {
34
+ return refuse('invalid_grant', 'The code is unknown, expired or already used.');
35
+ }
36
+ if (grant.clientId !== clientId || grant.redirectUri !== redirectUri) {
37
+ return refuse('invalid_grant', 'The code was issued to another client or redirect_uri.');
38
+ }
39
+ if (createHash('sha256').update(verifier).digest('base64url') !== grant.codeChallenge) {
40
+ return refuse('invalid_grant', 'The code_verifier does not match the code_challenge.');
41
+ }
42
+ return { ok: true, grant };
43
+ }
44
+ function refuse(error, description) {
45
+ return { ok: false, error, description };
46
+ }
47
+ /**
48
+ * What profile redemption hands back for `grant`, signed in as `user`. The
49
+ * token endpoint answers it beside the token, for a code or a refresh.
50
+ *
51
+ * The name, url and photo come with the profile scope and the email with the
52
+ * email scope; the IndieAuth spec keeps all four under `profile`. The url is
53
+ * the me, the page that already carries this person's h-card.
54
+ */
55
+ export function profileResponse(grant, user, baseUrl) {
56
+ const photo = user.profile?.avatar === undefined ? undefined : avatarUrl(user.profile.avatar, baseUrl);
57
+ const profile = {
58
+ ...(grant.scopes.includes('profile')
59
+ ? {
60
+ name: user.profile?.displayName ?? user.username,
61
+ url: grant.me,
62
+ ...(photo === undefined ? {} : { photo }),
63
+ }
64
+ : {}),
65
+ ...(grant.scopes.includes('email') && user.email !== undefined ? { email: user.email } : {}),
66
+ };
67
+ return Object.keys(profile).length === 0 ? { me: grant.me } : { me: grant.me, profile };
68
+ }
69
+ //# sourceMappingURL=redeem.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redeem.js","sourceRoot":"","sources":["../../src/indieauth/redeem.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import { SCOPES } from './discovery.ts';
2
+ /** A scope this site can grant. */
3
+ export type Scope = (typeof SCOPES)[number];
4
+ /** A sign-in a client asked for, every field checked. */
5
+ export interface AuthorizationRequest {
6
+ /** The client's URL, which is its identifier. */
7
+ readonly clientId: string;
8
+ /** Where the person is sent back with the answer. */
9
+ readonly redirectUri: string;
10
+ /** Handed back untouched, so the client can match the answer to its request. */
11
+ readonly state: string;
12
+ /** The S256 PKCE challenge the code will be bound to. */
13
+ readonly codeChallenge: string;
14
+ /** The scopes asked for that this site offers, in the order asked. */
15
+ readonly scopes: readonly Scope[];
16
+ /** The URL the person typed into the client, when the client passed it on. */
17
+ readonly me?: string;
18
+ /** The RFC 8707 resource the token is wanted for, when the client named one. */
19
+ readonly resource?: string;
20
+ }
21
+ /** The OAuth error codes a refused request is reported with. */
22
+ export type AuthorizationError = 'invalid_request' | 'unsupported_response_type' | 'invalid_target';
23
+ /**
24
+ * What a request to the authorization endpoint came to.
25
+ *
26
+ * `unredirectable` is a fault in `client_id` or `redirect_uri`: there is no
27
+ * address known to be the client's, so the person is shown a page. `refused`
28
+ * is any other fault, reported at `redirect_uri` once the client's metadata
29
+ * shows that address is the client's.
30
+ */
31
+ export type ParsedAuthorizationRequest = {
32
+ readonly kind: 'valid';
33
+ readonly request: AuthorizationRequest;
34
+ } | {
35
+ readonly kind: 'refused';
36
+ readonly clientId: string;
37
+ readonly redirectUri: string;
38
+ readonly state: string | undefined;
39
+ readonly error: AuthorizationError;
40
+ readonly description: string;
41
+ } | {
42
+ readonly kind: 'unredirectable';
43
+ readonly message: string;
44
+ };
45
+ /** Read an authorization request off its query string, for a site at `baseUrl`. */
46
+ export declare function parseAuthorizationRequest(params: URLSearchParams, baseUrl: string): ParsedAuthorizationRequest;
47
+ //# sourceMappingURL=request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/indieauth/request.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAExC,mCAAmC;AACnC,MAAM,MAAM,KAAK,GAAG,CAAC,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5C,yDAAyD;AACzD,MAAM,WAAW,oBAAoB;IACnC,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,qDAAqD;IACrD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yDAAyD;IACzD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,sEAAsE;IACtE,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC;IAClC,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,gEAAgE;AAChE,MAAM,MAAM,kBAAkB,GAAG,iBAAiB,GAAG,2BAA2B,GAAG,gBAAgB,CAAC;AAEpG;;;;;;;GAOG;AACH,MAAM,MAAM,0BAA0B,GAClC;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAA;CAAE,GAClE;IACE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAC;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAelE,mFAAmF;AACnF,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,eAAe,EACvB,OAAO,EAAE,MAAM,GACd,0BAA0B,CAyD5B"}
@@ -0,0 +1,104 @@
1
+ import { isIP } from 'node:net';
2
+ import { SCOPES } from './discovery.js';
3
+ /** An S256 challenge: 32 bytes of hash in unpadded base64url. */
4
+ const S256_CHALLENGE = /^[A-Za-z0-9_-]{43}$/;
5
+ /** Schemes a redirect may never use, whatever a client's metadata lists. */
6
+ const SCRIPTING_SCHEMES = new Set([
7
+ 'javascript:',
8
+ 'data:',
9
+ 'vbscript:',
10
+ 'file:',
11
+ 'blob:',
12
+ 'about:',
13
+ ]);
14
+ /** Read an authorization request off its query string, for a site at `baseUrl`. */
15
+ export function parseAuthorizationRequest(params, baseUrl) {
16
+ const clientId = clientIdentifier(params.get('client_id') ?? '');
17
+ if (clientId === undefined) {
18
+ return { kind: 'unredirectable', message: 'The app did not say which app it is.' };
19
+ }
20
+ const redirectUri = redirectAddress(params.get('redirect_uri') ?? '');
21
+ if (redirectUri === undefined) {
22
+ return { kind: 'unredirectable', message: 'The app did not say where to send you back.' };
23
+ }
24
+ const state = params.get('state') ?? undefined;
25
+ const refuse = (error, description) => ({
26
+ kind: 'refused',
27
+ clientId,
28
+ redirectUri,
29
+ state,
30
+ error,
31
+ description,
32
+ });
33
+ if (params.get('response_type') !== 'code') {
34
+ return refuse('unsupported_response_type', 'response_type must be code');
35
+ }
36
+ if (state === undefined || state === '')
37
+ return refuse('invalid_request', 'state is required');
38
+ const codeChallenge = params.get('code_challenge') ?? '';
39
+ if (!S256_CHALLENGE.test(codeChallenge)) {
40
+ return refuse('invalid_request', 'code_challenge must be an S256 PKCE challenge');
41
+ }
42
+ if (params.get('code_challenge_method') !== 'S256') {
43
+ return refuse('invalid_request', 'code_challenge_method must be S256');
44
+ }
45
+ const resources = params.getAll('resource');
46
+ if (resources.length > 1)
47
+ return refuse('invalid_target', 'name at most one resource');
48
+ const resource = resources[0];
49
+ if (resource !== undefined && !onSite(resource, baseUrl)) {
50
+ return refuse('invalid_target', 'resource must be an absolute URL on this site');
51
+ }
52
+ const offered = new Set(SCOPES);
53
+ const scopes = [...new Set((params.get('scope') ?? '').split(' '))].filter((scope) => offered.has(scope));
54
+ const me = params.get('me') ?? '';
55
+ return {
56
+ kind: 'valid',
57
+ request: {
58
+ clientId,
59
+ redirectUri,
60
+ state,
61
+ codeChallenge,
62
+ scopes,
63
+ ...(me === '' ? {} : { me }),
64
+ ...(resource === undefined ? {} : { resource }),
65
+ },
66
+ };
67
+ }
68
+ /**
69
+ * A client_id the IndieAuth spec allows, as given, or `undefined`.
70
+ *
71
+ * http or https, with no fragment, credentials or dot segments, and a domain
72
+ * name for a host: an IP address only when it is the loopback one a client
73
+ * in development runs on.
74
+ */
75
+ function clientIdentifier(value) {
76
+ const url = URL.parse(value);
77
+ if (url === null || (url.protocol !== 'https:' && url.protocol !== 'http:'))
78
+ return undefined;
79
+ if (url.username !== '' || url.password !== '' || value.includes('#'))
80
+ return undefined;
81
+ const path = value
82
+ .slice(url.protocol.length + 2)
83
+ .replace(/^[^/?]*/, '')
84
+ .split('?')[0] ?? '';
85
+ if (path.split('/').some((segment) => segment === '.' || segment === '..'))
86
+ return undefined;
87
+ const host = url.hostname.replace(/^\[|\]$/g, '');
88
+ if (isIP(host) !== 0 && host !== '127.0.0.1' && host !== '::1')
89
+ return undefined;
90
+ return value;
91
+ }
92
+ /** An absolute redirect_uri with no fragment and no scripting scheme, or `undefined`. */
93
+ function redirectAddress(value) {
94
+ const url = URL.parse(value);
95
+ if (url === null || value.includes('#') || SCRIPTING_SCHEMES.has(url.protocol))
96
+ return undefined;
97
+ return value;
98
+ }
99
+ /** Whether `value` is an absolute URL, with no fragment, on the site's own origin. */
100
+ function onSite(value, baseUrl) {
101
+ const url = URL.parse(value);
102
+ return url !== null && !value.includes('#') && url.origin === new URL(baseUrl).origin;
103
+ }
104
+ //# sourceMappingURL=request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request.js","sourceRoot":"","sources":["../../src/indieauth/request.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { Hono } from 'hono';
2
+ import type { GeekityEnv } from '../env.ts';
3
+ /**
4
+ * The token endpoint (TASK-160): a client redeems its code here for a bearer
5
+ * token, or trades a refresh token for a new pair.
6
+ *
7
+ * A code is checked by the same {@link redeemCode} profile redemption uses, so
8
+ * a code spent at one endpoint is spent at both. Outside `/admin`, because the
9
+ * client posting here has no admin session, and not exempt from maintenance
10
+ * mode, like the authorization endpoint beside it.
11
+ */
12
+ export declare function mountTokenEndpoint(app: Hono<GeekityEnv>): void;
13
+ /**
14
+ * The endpoints that answer for a token once it is issued (TASK-161):
15
+ * introspection, revocation and userinfo. Outside `/admin` and behind the
16
+ * maintenance gate, like the token endpoint.
17
+ */
18
+ export declare function mountTokenInfoEndpoints(app: Hono<GeekityEnv>): void;
19
+ //# sourceMappingURL=token.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/indieauth/token.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,MAAM,CAAC;AAI1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAoB5C;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,CA2B9D;AA0BD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,CA6CnE"}
@@ -0,0 +1,110 @@
1
+ import { findUserById } from '../admin/accounts.js';
2
+ import { requireBearer } from './bearer.js';
3
+ import { INTROSPECTION_PATH, REVOCATION_PATH, TOKEN_PATH, USERINFO_PATH, siteBaseUrl, } from './discovery.js';
4
+ import { profileResponse, redeemCode, redemptionForm } from './redeem.js';
5
+ import { ACCESS_TOKEN_LIFETIME_MS, findAccessToken, issueTokens, refreshTokens, revokeToken, } from './tokens.js';
6
+ /**
7
+ * The token endpoint (TASK-160): a client redeems its code here for a bearer
8
+ * token, or trades a refresh token for a new pair.
9
+ *
10
+ * A code is checked by the same {@link redeemCode} profile redemption uses, so
11
+ * a code spent at one endpoint is spent at both. Outside `/admin`, because the
12
+ * client posting here has no admin session, and not exempt from maintenance
13
+ * mode, like the authorization endpoint beside it.
14
+ */
15
+ export function mountTokenEndpoint(app) {
16
+ app.post(TOKEN_PATH, async (c) => {
17
+ c.header('cache-control', 'no-store');
18
+ const form = redemptionForm(await c.req.parseBody());
19
+ const now = c.var.config.now();
20
+ if (form['grant_type'] === 'refresh_token') {
21
+ const refreshed = await refreshTokens(c.var.config.dataDir, form, now);
22
+ if (!refreshed.ok)
23
+ return refuse(c, refreshed.error, refreshed.description);
24
+ const user = findUserById(c.var.config.dataDir, refreshed.issued.token.userId);
25
+ if (user === undefined)
26
+ return refuse(c, 'invalid_grant', GONE);
27
+ return answer(c, refreshed.issued, user);
28
+ }
29
+ const redeemed = redeemCode(c.var.indieauth.codes, form);
30
+ if (!redeemed.ok)
31
+ return refuse(c, redeemed.error, redeemed.description);
32
+ const { grant } = redeemed;
33
+ if (grant.scopes.length === 0) {
34
+ return refuse(c, 'invalid_grant', 'The code was approved with no scope, so earns no token.');
35
+ }
36
+ if (!sameResource(form, grant.resource)) {
37
+ return refuse(c, 'invalid_target', 'resource must be the one the person approved.');
38
+ }
39
+ const user = findUserById(c.var.config.dataDir, grant.userId);
40
+ if (user === undefined)
41
+ return refuse(c, 'invalid_grant', GONE);
42
+ return answer(c, await issueTokens(c.var.config.dataDir, grant, now), user);
43
+ });
44
+ }
45
+ const GONE = 'The person who approved this is gone.';
46
+ /** Whether the request names no resource, or the one the code was approved for (RFC 8707). */
47
+ function sameResource(form, approved) {
48
+ const named = form['resource'];
49
+ return named === undefined || named === approved;
50
+ }
51
+ function answer(c, issued, user) {
52
+ const { token } = issued;
53
+ return c.json({
54
+ access_token: issued.accessToken,
55
+ token_type: 'Bearer',
56
+ scope: token.scopes.join(' '),
57
+ expires_in: ACCESS_TOKEN_LIFETIME_MS / 1000,
58
+ refresh_token: issued.refreshToken,
59
+ ...profileResponse(token, user, siteBaseUrl(c)),
60
+ });
61
+ }
62
+ function refuse(c, error, description) {
63
+ return c.json({ error, error_description: description }, 400);
64
+ }
65
+ /**
66
+ * The endpoints that answer for a token once it is issued (TASK-161):
67
+ * introspection, revocation and userinfo. Outside `/admin` and behind the
68
+ * maintenance gate, like the token endpoint.
69
+ */
70
+ export function mountTokenInfoEndpoints(app) {
71
+ // RFC 7662. The IndieAuth spec asks that introspection need authorization
72
+ // of its own: here, another live token, and it answers only for tokens held
73
+ // by the same person, so one person's token can never tell anything about
74
+ // another's.
75
+ app.post(INTROSPECTION_PATH, requireBearer({ audience: 'authorization-server' }), async (c) => {
76
+ c.header('cache-control', 'no-store');
77
+ const presented = (await c.req.parseBody())['token'];
78
+ if (typeof presented !== 'string' || presented === '') {
79
+ return refuse(c, 'invalid_request', 'token is required.');
80
+ }
81
+ const token = findAccessToken(c.var.config.dataDir, presented, c.var.config.now());
82
+ if (token === undefined || token.userId !== c.var.bearer.user.id) {
83
+ return c.json({ active: false });
84
+ }
85
+ return c.json({
86
+ active: true,
87
+ me: token.me,
88
+ client_id: token.clientId,
89
+ scope: token.scopes.join(' '),
90
+ exp: Math.floor(Date.parse(token.expiresAt) / 1000),
91
+ });
92
+ });
93
+ // RFC 7009: holding the token is the authorization, and the answer is 200
94
+ // whether or not it was one, so a client learns nothing by guessing.
95
+ app.post(REVOCATION_PATH, async (c) => {
96
+ c.header('cache-control', 'no-store');
97
+ const presented = (await c.req.parseBody())['token'];
98
+ if (typeof presented !== 'string' || presented === '') {
99
+ return refuse(c, 'invalid_request', 'token is required.');
100
+ }
101
+ await revokeToken(c.var.config.dataDir, presented, c.var.config.now());
102
+ return c.body(null, 200);
103
+ });
104
+ app.get(USERINFO_PATH, requireBearer({ audience: 'authorization-server', scope: 'profile' }), (c) => {
105
+ c.header('cache-control', 'no-store');
106
+ const { token, user } = c.var.bearer;
107
+ return c.json(profileResponse(token, user, siteBaseUrl(c)).profile ?? {});
108
+ });
109
+ }
110
+ //# sourceMappingURL=token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/indieauth/token.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,131 @@
1
+ import type { AuthorizationCode } from './grants.ts';
2
+ import type { RedemptionForm } from './redeem.ts';
3
+ import type { Scope } from './request.ts';
4
+ /**
5
+ * The access tokens the token endpoint has issued: `data/indieauth-tokens.json`
6
+ * (TASK-160, decision-24).
7
+ *
8
+ * A token is irreducible state under decision-9: a client holding one cannot
9
+ * be handed it again, so losing it signs the person out of every app. So it
10
+ * lives in a file in `data/`, written 0600 like `users.json`, and holds a
11
+ * SHA-256 hash of each token rather than the token. A token is 32 random
12
+ * bytes, so the hash needs no salt: nobody can guess their way back to one.
13
+ *
14
+ * One record per connection, that is per approval of a client: a refresh
15
+ * replaces both hashes in place, so the record keeps the id and the time the
16
+ * person approved it while the tokens change under it.
17
+ */
18
+ /** The file, relative to `dataDir`. */
19
+ export declare const TOKENS_FILE = "indieauth-tokens.json";
20
+ /** The permissions the file is written with: nobody but the site's own user. */
21
+ export declare const TOKENS_FILE_MODE = 384;
22
+ /** How long an access token works for. */
23
+ export declare const ACCESS_TOKEN_LIFETIME_MS: number;
24
+ /** How long a refresh token works for, counted from its own issue: each refresh starts it again. */
25
+ export declare const REFRESH_TOKEN_LIFETIME_MS: number;
26
+ /**
27
+ * How stale a connection's last use may be. A token's use is written only
28
+ * when the stored one is older than this, so a client calling the API all
29
+ * day rewrites the file once an hour rather than once a request (TASK-162,
30
+ * decision-24).
31
+ */
32
+ export declare const LAST_USE_RESOLUTION_MS: number;
33
+ /** One connection as the file keeps it. */
34
+ export interface StoredToken {
35
+ /** Stable for the life of the connection, so a screen can name it. */
36
+ readonly id: string;
37
+ readonly userId: number;
38
+ /** The canonical me URL the client was handed. */
39
+ readonly me: string;
40
+ readonly clientId: string;
41
+ /** What the client called itself on the consent screen, when it said. */
42
+ readonly clientName?: string;
43
+ readonly scopes: readonly Scope[];
44
+ /** The RFC 8707 resource the token is good at, or absent when the client named none. */
45
+ readonly resource?: string;
46
+ /** When the person approved the connection, as an ISO 8601 instant. */
47
+ readonly issuedAt: string;
48
+ /** When a resource server last accepted its token, to {@link LAST_USE_RESOLUTION_MS}. */
49
+ readonly lastUsedAt?: string;
50
+ readonly accessTokenHash: string;
51
+ /** When the current access token stops working. */
52
+ readonly expiresAt: string;
53
+ readonly refreshTokenHash: string;
54
+ /** When the current refresh token stops working. */
55
+ readonly refreshExpiresAt: string;
56
+ }
57
+ /** Tokens just issued: the only time the tokens themselves exist anywhere. */
58
+ export interface IssuedTokens {
59
+ readonly accessToken: string;
60
+ readonly refreshToken: string;
61
+ readonly token: StoredToken;
62
+ }
63
+ /**
64
+ * Who is asking a token to be checked: a resource server, by the resource
65
+ * identifier clients name it with.
66
+ *
67
+ * `acceptsUnbound` says whether it takes a token issued with no resource
68
+ * named. Micropub clients predate RFC 8707 and name none, so the Micropub
69
+ * endpoint will; MCP clients always name one, and the MCP spec asks a server
70
+ * to take only tokens issued for it, so the MCP endpoint will not.
71
+ */
72
+ export interface Audience {
73
+ readonly resource: string;
74
+ readonly acceptsUnbound: boolean;
75
+ }
76
+ export type Refresh = {
77
+ readonly ok: true;
78
+ readonly issued: IssuedTokens;
79
+ } | {
80
+ readonly ok: false;
81
+ readonly error: 'invalid_request' | 'invalid_grant';
82
+ readonly description: string;
83
+ };
84
+ /** Where the tokens file lives for a given data directory. */
85
+ export declare function tokensFile(dataDir: string): string;
86
+ /** Every connection in the file, live or not. */
87
+ export declare function listTokens(dataDir: string): StoredToken[];
88
+ /** Issue an access token and a refresh token for an approved code. */
89
+ export declare function issueTokens(dataDir: string, grant: AuthorizationCode, now: Date): Promise<IssuedTokens>;
90
+ /**
91
+ * Trade a refresh token for new tokens, from the client it was issued to.
92
+ *
93
+ * Both tokens rotate: the old access token and the old refresh token stop
94
+ * working the moment this returns. The scopes and resource stay the ones the
95
+ * person approved; a `scope` in the request is ignored, which RFC 6749 allows
96
+ * because the answer says the scope it holds.
97
+ */
98
+ export declare function refreshTokens(dataDir: string, form: RedemptionForm, now: Date): Promise<Refresh>;
99
+ /**
100
+ * The connection a live access token belongs to, whoever is asking; the
101
+ * authorization server's own endpoints take any token it issued. `undefined`
102
+ * for a token that is unknown or expired.
103
+ */
104
+ export declare function findAccessToken(dataDir: string, accessToken: string, now: Date): StoredToken | undefined;
105
+ /**
106
+ * The connection a live access token belongs to, when `audience` may accept
107
+ * it; `undefined` for a token that is unknown, expired or bound to another
108
+ * resource.
109
+ */
110
+ export declare function verifyAccessToken(dataDir: string, accessToken: string, audience: Audience, now: Date): StoredToken | undefined;
111
+ /**
112
+ * End the connection `presented` belongs to, whether it is the access token
113
+ * or the refresh token, so both stop working at once. Answers whether there
114
+ * was one.
115
+ */
116
+ export declare function revokeToken(dataDir: string, presented: string, now: Date): Promise<boolean>;
117
+ /**
118
+ * End connection `id` of `userId`, as the connected apps screen does, and
119
+ * answer what it was. Somebody else's connection, or one already gone, answers
120
+ * `undefined` and writes nothing.
121
+ */
122
+ export declare function revokeConnection(dataDir: string, userId: number, id: string, now: Date): Promise<StoredToken | undefined>;
123
+ /**
124
+ * Note that `token` was just accepted, unless its last use is already within
125
+ * {@link LAST_USE_RESOLUTION_MS}: the record says when it was last written,
126
+ * so no state outside the file is needed to keep the writes rare.
127
+ */
128
+ export declare function recordUse(dataDir: string, token: StoredToken, now: Date): Promise<void>;
129
+ /** Revoke every token `userId` holds. */
130
+ export declare function revokeTokensForUser(dataDir: string, userId: number): Promise<void>;
131
+ //# sourceMappingURL=tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/indieauth/tokens.ts"],"names":[],"mappings":"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"}