@geekity/cms 0.3.0 → 0.4.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.
- package/README.md +2091 -214
- package/admin/components/fields.njk +165 -0
- package/admin/layouts/base.njk +1 -1
- package/admin/layouts/settings-page.njk +26 -0
- package/admin/layouts/shell.njk +17 -3
- package/admin/pages/account/forgot.njk +47 -0
- package/admin/pages/account/login.njk +26 -0
- package/admin/pages/account/reset.njk +38 -0
- package/admin/pages/account/setup.njk +28 -0
- package/admin/pages/appearance/themes.njk +69 -0
- package/admin/pages/comments/all.njk +133 -0
- package/admin/{layouts/dashboard.njk → pages/dashboard/home.njk} +8 -15
- package/admin/{layouts/document-conflict.njk → pages/documents/conflict.njk} +1 -0
- package/admin/pages/documents/editor.njk +196 -0
- package/admin/{layouts/document-list.njk → pages/documents/list.njk} +19 -2
- package/admin/pages/documents/taxonomy.njk +95 -0
- package/admin/pages/federation/followers.njk +238 -0
- package/admin/pages/media/library.njk +160 -0
- package/admin/pages/messages/all.njk +110 -0
- package/admin/{layouts → pages}/placeholder.njk +5 -1
- package/admin/pages/settings/discussion.njk +99 -0
- package/admin/pages/settings/email.njk +203 -0
- package/admin/pages/settings/federation.njk +75 -0
- package/admin/pages/settings/general.njk +29 -0
- package/admin/pages/settings/permalinks.njk +50 -0
- package/admin/pages/settings/reading.njk +72 -0
- package/admin/pages/users/edit.njk +239 -0
- package/admin/pages/users/list.njk +48 -0
- package/admin/pages/users/new.njk +41 -0
- package/admin/static/admin.css +498 -3
- package/admin/static/copy.js +52 -0
- package/admin/static/editor.js +16 -16
- package/admin/static/slug.js +61 -0
- package/dist/admin/accounts.d.ts +413 -0
- package/dist/admin/accounts.d.ts.map +1 -0
- package/dist/admin/accounts.js +680 -0
- package/dist/admin/accounts.js.map +1 -0
- package/dist/admin/appearance.d.ts +80 -0
- package/dist/admin/appearance.d.ts.map +1 -0
- package/dist/admin/appearance.js +136 -0
- package/dist/admin/appearance.js.map +1 -0
- package/dist/admin/comments.d.ts +90 -0
- package/dist/admin/comments.d.ts.map +1 -0
- package/dist/admin/comments.js +244 -0
- package/dist/admin/comments.js.map +1 -0
- package/dist/admin/credentials.d.ts +26 -2
- package/dist/admin/credentials.d.ts.map +1 -1
- package/dist/admin/credentials.js +35 -2
- package/dist/admin/credentials.js.map +1 -1
- package/dist/admin/documents.d.ts +127 -10
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +360 -41
- package/dist/admin/documents.js.map +1 -1
- package/dist/admin/federation.d.ts +151 -50
- package/dist/admin/federation.d.ts.map +1 -1
- package/dist/admin/federation.js +225 -93
- package/dist/admin/federation.js.map +1 -1
- package/dist/admin/formatting.d.ts +20 -0
- package/dist/admin/formatting.d.ts.map +1 -0
- package/dist/admin/formatting.js +72 -0
- package/dist/admin/formatting.js.map +1 -0
- package/dist/admin/headers.d.ts +65 -0
- package/dist/admin/headers.d.ts.map +1 -0
- package/dist/admin/headers.js +104 -0
- package/dist/admin/headers.js.map +1 -0
- package/dist/admin/index.d.ts +38 -12
- package/dist/admin/index.d.ts.map +1 -1
- package/dist/admin/index.js +25 -7
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/media.d.ts +168 -0
- package/dist/admin/media.d.ts.map +1 -0
- package/dist/admin/media.js +295 -0
- package/dist/admin/media.js.map +1 -0
- package/dist/admin/menu.d.ts +79 -0
- package/dist/admin/menu.d.ts.map +1 -0
- package/dist/admin/menu.js +126 -0
- package/dist/admin/menu.js.map +1 -0
- package/dist/admin/messages.d.ts +80 -0
- package/dist/admin/messages.d.ts.map +1 -0
- package/dist/admin/messages.js +152 -0
- package/dist/admin/messages.js.map +1 -0
- package/dist/admin/preview.d.ts.map +1 -1
- package/dist/admin/preview.js +31 -7
- package/dist/admin/preview.js.map +1 -1
- package/dist/admin/recovery.d.ts +92 -0
- package/dist/admin/recovery.d.ts.map +1 -0
- package/dist/admin/recovery.js +229 -0
- package/dist/admin/recovery.js.map +1 -0
- package/dist/admin/routes.d.ts +0 -20
- package/dist/admin/routes.d.ts.map +1 -1
- package/dist/admin/routes.js +155 -105
- package/dist/admin/routes.js.map +1 -1
- package/dist/admin/settings-discussion.d.ts +37 -0
- package/dist/admin/settings-discussion.d.ts.map +1 -0
- package/dist/admin/settings-discussion.js +117 -0
- package/dist/admin/settings-discussion.js.map +1 -0
- package/dist/admin/settings-email.d.ts +54 -0
- package/dist/admin/settings-email.d.ts.map +1 -0
- package/dist/admin/settings-email.js +189 -0
- package/dist/admin/settings-email.js.map +1 -0
- package/dist/admin/settings-federation.d.ts +39 -0
- package/dist/admin/settings-federation.d.ts.map +1 -0
- package/dist/admin/settings-federation.js +103 -0
- package/dist/admin/settings-federation.js.map +1 -0
- package/dist/admin/settings-general.d.ts +12 -0
- package/dist/admin/settings-general.d.ts.map +1 -0
- package/dist/admin/settings-general.js +48 -0
- package/dist/admin/settings-general.js.map +1 -0
- package/dist/admin/settings-page.d.ts +102 -0
- package/dist/admin/settings-page.d.ts.map +1 -0
- package/dist/admin/settings-page.js +126 -0
- package/dist/admin/settings-page.js.map +1 -0
- package/dist/admin/settings-pages.d.ts +19 -0
- package/dist/admin/settings-pages.d.ts.map +1 -0
- package/dist/admin/settings-pages.js +33 -0
- package/dist/admin/settings-pages.js.map +1 -0
- package/dist/admin/settings-permalinks.d.ts +16 -0
- package/dist/admin/settings-permalinks.d.ts.map +1 -0
- package/dist/admin/settings-permalinks.js +32 -0
- package/dist/admin/settings-permalinks.js.map +1 -0
- package/dist/admin/settings-reading.d.ts +33 -0
- package/dist/admin/settings-reading.d.ts.map +1 -0
- package/dist/admin/settings-reading.js +48 -0
- package/dist/admin/settings-reading.js.map +1 -0
- package/dist/admin/settings.d.ts +325 -96
- package/dist/admin/settings.d.ts.map +1 -1
- package/dist/admin/settings.js +650 -308
- package/dist/admin/settings.js.map +1 -1
- package/dist/admin/store.d.ts +606 -152
- package/dist/admin/store.d.ts.map +1 -1
- package/dist/admin/store.js +863 -268
- package/dist/admin/store.js.map +1 -1
- package/dist/admin/taxonomy.d.ts +114 -0
- package/dist/admin/taxonomy.d.ts.map +1 -0
- package/dist/admin/taxonomy.js +289 -0
- package/dist/admin/taxonomy.js.map +1 -0
- package/dist/admin/templates.d.ts +44 -14
- package/dist/admin/templates.d.ts.map +1 -1
- package/dist/admin/templates.js +44 -14
- package/dist/admin/templates.js.map +1 -1
- package/dist/admin/throttle.d.ts +84 -0
- package/dist/admin/throttle.d.ts.map +1 -0
- package/dist/admin/throttle.js +143 -0
- package/dist/admin/throttle.js.map +1 -0
- package/dist/admin/uploads.d.ts +28 -2
- package/dist/admin/uploads.d.ts.map +1 -1
- package/dist/admin/uploads.js +43 -4
- package/dist/admin/uploads.js.map +1 -1
- package/dist/admin/users.d.ts +100 -13
- package/dist/admin/users.d.ts.map +1 -1
- package/dist/admin/users.js +412 -39
- package/dist/admin/users.js.map +1 -1
- package/dist/cache.d.ts +141 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +207 -0
- package/dist/cache.js.map +1 -0
- package/dist/cli.d.ts +14 -2
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +424 -21
- package/dist/cli.js.map +1 -1
- package/dist/comments/akismet.d.ts +130 -0
- package/dist/comments/akismet.d.ts.map +1 -0
- package/dist/comments/akismet.js +274 -0
- package/dist/comments/akismet.js.map +1 -0
- package/dist/comments/conversation.d.ts +29 -0
- package/dist/comments/conversation.d.ts.map +1 -0
- package/dist/comments/conversation.js +60 -0
- package/dist/comments/conversation.js.map +1 -0
- package/dist/comments/form.d.ts +75 -0
- package/dist/comments/form.d.ts.map +1 -0
- package/dist/comments/form.js +64 -0
- package/dist/comments/form.js.map +1 -0
- package/dist/comments/index.d.ts +15 -0
- package/dist/comments/index.d.ts.map +1 -0
- package/dist/comments/index.js +9 -0
- package/dist/comments/index.js.map +1 -0
- package/dist/comments/markdown.d.ts +8 -0
- package/dist/comments/markdown.d.ts.map +1 -0
- package/dist/comments/markdown.js +85 -0
- package/dist/comments/markdown.js.map +1 -0
- package/dist/comments/moderate.d.ts +71 -0
- package/dist/comments/moderate.d.ts.map +1 -0
- package/dist/comments/moderate.js +88 -0
- package/dist/comments/moderate.js.map +1 -0
- package/dist/comments/policy.d.ts +57 -0
- package/dist/comments/policy.d.ts.map +1 -0
- package/dist/comments/policy.js +80 -0
- package/dist/comments/policy.js.map +1 -0
- package/dist/comments/records.d.ts +266 -0
- package/dist/comments/records.d.ts.map +1 -0
- package/dist/comments/records.js +487 -0
- package/dist/comments/records.js.map +1 -0
- package/dist/comments/routes.d.ts +58 -0
- package/dist/comments/routes.d.ts.map +1 -0
- package/dist/comments/routes.js +177 -0
- package/dist/comments/routes.js.map +1 -0
- package/dist/comments/submission.d.ts +263 -0
- package/dist/comments/submission.d.ts.map +1 -0
- package/dist/comments/submission.js +241 -0
- package/dist/comments/submission.js.map +1 -0
- package/dist/config.d.ts +190 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +118 -6
- package/dist/config.js.map +1 -1
- package/dist/contact/delivery.d.ts +60 -0
- package/dist/contact/delivery.d.ts.map +1 -0
- package/dist/contact/delivery.js +87 -0
- package/dist/contact/delivery.js.map +1 -0
- package/dist/contact/form.d.ts +130 -0
- package/dist/contact/form.d.ts.map +1 -0
- package/dist/contact/form.js +163 -0
- package/dist/contact/form.js.map +1 -0
- package/dist/contact/index.d.ts +17 -0
- package/dist/contact/index.d.ts.map +1 -0
- package/dist/contact/index.js +13 -0
- package/dist/contact/index.js.map +1 -0
- package/dist/contact/records.d.ts +124 -0
- package/dist/contact/records.d.ts.map +1 -0
- package/dist/contact/records.js +218 -0
- package/dist/contact/records.js.map +1 -0
- package/dist/contact/routes.d.ts +17 -0
- package/dist/contact/routes.d.ts.map +1 -0
- package/dist/contact/routes.js +127 -0
- package/dist/contact/routes.js.map +1 -0
- package/dist/contact/submission.d.ts +103 -0
- package/dist/contact/submission.d.ts.map +1 -0
- package/dist/contact/submission.js +166 -0
- package/dist/contact/submission.js.map +1 -0
- package/dist/content/document.d.ts +7 -2
- package/dist/content/document.d.ts.map +1 -1
- package/dist/content/document.js +1 -0
- package/dist/content/document.js.map +1 -1
- package/dist/content/index.d.ts +7 -2
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +4 -1
- package/dist/content/index.js.map +1 -1
- package/dist/content/markdown.d.ts.map +1 -1
- package/dist/content/markdown.js +24 -2
- package/dist/content/markdown.js.map +1 -1
- package/dist/content/parser.d.ts.map +1 -1
- package/dist/content/parser.js +11 -4
- package/dist/content/parser.js.map +1 -1
- package/dist/content/save.d.ts +5 -0
- package/dist/content/save.d.ts.map +1 -1
- package/dist/content/save.js +44 -1
- package/dist/content/save.js.map +1 -1
- package/dist/content/schedule.d.ts +125 -0
- package/dist/content/schedule.d.ts.map +1 -0
- package/dist/content/schedule.js +207 -0
- package/dist/content/schedule.js.map +1 -0
- package/dist/content/search.d.ts +64 -0
- package/dist/content/search.d.ts.map +1 -0
- package/dist/content/search.js +136 -0
- package/dist/content/search.js.map +1 -0
- package/dist/content/store.d.ts +202 -7
- package/dist/content/store.d.ts.map +1 -1
- package/dist/content/store.js +374 -67
- package/dist/content/store.js.map +1 -1
- package/dist/content/sync.d.ts +7 -1
- package/dist/content/sync.d.ts.map +1 -1
- package/dist/content/sync.js +18 -10
- package/dist/content/sync.js.map +1 -1
- package/dist/content/time.d.ts +56 -0
- package/dist/content/time.d.ts.map +1 -0
- package/dist/content/time.js +141 -0
- package/dist/content/time.js.map +1 -0
- package/dist/content/writer.d.ts.map +1 -1
- package/dist/content/writer.js +3 -0
- package/dist/content/writer.js.map +1 -1
- package/dist/env.d.ts +58 -4
- package/dist/env.d.ts.map +1 -1
- package/dist/federation/actor.d.ts +95 -41
- package/dist/federation/actor.d.ts.map +1 -1
- package/dist/federation/actor.js +180 -53
- package/dist/federation/actor.js.map +1 -1
- package/dist/federation/article.d.ts +44 -17
- package/dist/federation/article.d.ts.map +1 -1
- package/dist/federation/article.js +98 -46
- package/dist/federation/article.js.map +1 -1
- package/dist/federation/delivery.d.ts +63 -15
- package/dist/federation/delivery.d.ts.map +1 -1
- package/dist/federation/delivery.js +154 -80
- package/dist/federation/delivery.js.map +1 -1
- package/dist/federation/federation.d.ts +22 -17
- package/dist/federation/federation.d.ts.map +1 -1
- package/dist/federation/federation.js +98 -77
- package/dist/federation/federation.js.map +1 -1
- package/dist/federation/followers.d.ts +3 -2
- package/dist/federation/followers.d.ts.map +1 -1
- package/dist/federation/followers.js +6 -5
- package/dist/federation/followers.js.map +1 -1
- package/dist/federation/import-wordpress.d.ts +128 -0
- package/dist/federation/import-wordpress.d.ts.map +1 -0
- package/dist/federation/import-wordpress.js +426 -0
- package/dist/federation/import-wordpress.js.map +1 -0
- package/dist/federation/inbox.d.ts +36 -13
- package/dist/federation/inbox.d.ts.map +1 -1
- package/dist/federation/inbox.js +120 -49
- package/dist/federation/inbox.js.map +1 -1
- package/dist/federation/index.d.ts +22 -10
- package/dist/federation/index.d.ts.map +1 -1
- package/dist/federation/index.js +13 -8
- package/dist/federation/index.js.map +1 -1
- package/dist/federation/keys.d.ts +98 -20
- package/dist/federation/keys.d.ts.map +1 -1
- package/dist/federation/keys.js +269 -39
- package/dist/federation/keys.js.map +1 -1
- package/dist/federation/mount.d.ts +42 -10
- package/dist/federation/mount.d.ts.map +1 -1
- package/dist/federation/mount.js +266 -26
- package/dist/federation/mount.js.map +1 -1
- package/dist/federation/paths.d.ts +23 -35
- package/dist/federation/paths.d.ts.map +1 -1
- package/dist/federation/paths.js +26 -39
- package/dist/federation/paths.js.map +1 -1
- package/dist/federation/records.d.ts +223 -0
- package/dist/federation/records.d.ts.map +1 -0
- package/dist/federation/records.js +506 -0
- package/dist/federation/records.js.map +1 -0
- package/dist/federation/relays.d.ts +114 -0
- package/dist/federation/relays.d.ts.map +1 -0
- package/dist/federation/relays.js +290 -0
- package/dist/federation/relays.js.map +1 -0
- package/dist/federation/replies.d.ts +80 -0
- package/dist/federation/replies.d.ts.map +1 -0
- package/dist/federation/replies.js +166 -0
- package/dist/federation/replies.js.map +1 -0
- package/dist/federation/wordpress.d.ts +131 -0
- package/dist/federation/wordpress.d.ts.map +1 -0
- package/dist/federation/wordpress.js +341 -0
- package/dist/federation/wordpress.js.map +1 -0
- package/dist/files/atomic.d.ts +103 -0
- package/dist/files/atomic.d.ts.map +1 -0
- package/dist/files/atomic.js +150 -0
- package/dist/files/atomic.js.map +1 -0
- package/dist/files/index.d.ts +3 -0
- package/dist/files/index.d.ts.map +1 -0
- package/dist/files/index.js +2 -0
- package/dist/files/index.js.map +1 -0
- package/dist/forms/protection.d.ts +85 -0
- package/dist/forms/protection.d.ts.map +1 -0
- package/dist/forms/protection.js +116 -0
- package/dist/forms/protection.js.map +1 -0
- package/dist/images/icons.d.ts +59 -0
- package/dist/images/icons.d.ts.map +1 -0
- package/dist/images/icons.js +131 -0
- package/dist/images/icons.js.map +1 -0
- package/dist/images/index.d.ts +7 -0
- package/dist/images/index.d.ts.map +1 -0
- package/dist/images/index.js +4 -0
- package/dist/images/index.js.map +1 -0
- package/dist/images/markup.d.ts +47 -0
- package/dist/images/markup.d.ts.map +1 -0
- package/dist/images/markup.js +175 -0
- package/dist/images/markup.js.map +1 -0
- package/dist/images/paths.d.ts +34 -0
- package/dist/images/paths.d.ts.map +1 -0
- package/dist/images/paths.js +41 -0
- package/dist/images/paths.js.map +1 -0
- package/dist/images/variants.d.ts +104 -0
- package/dist/images/variants.d.ts.map +1 -0
- package/dist/images/variants.js +320 -0
- package/dist/images/variants.js.map +1 -0
- package/dist/index.d.ts +123 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +327 -25
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +18 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +34 -3
- package/dist/init.js.map +1 -1
- package/dist/mail/brevo.d.ts +35 -0
- package/dist/mail/brevo.d.ts.map +1 -0
- package/dist/mail/brevo.js +85 -0
- package/dist/mail/brevo.js.map +1 -0
- package/dist/mail/credentials.d.ts +62 -0
- package/dist/mail/credentials.d.ts.map +1 -0
- package/dist/mail/credentials.js +86 -0
- package/dist/mail/credentials.js.map +1 -0
- package/dist/mail/index.d.ts +15 -0
- package/dist/mail/index.d.ts.map +1 -0
- package/dist/mail/index.js +8 -0
- package/dist/mail/index.js.map +1 -0
- package/dist/mail/memory.d.ts +35 -0
- package/dist/mail/memory.d.ts.map +1 -0
- package/dist/mail/memory.js +29 -0
- package/dist/mail/memory.js.map +1 -0
- package/dist/mail/provider.d.ts +80 -0
- package/dist/mail/provider.d.ts.map +1 -0
- package/dist/mail/provider.js +23 -0
- package/dist/mail/provider.js.map +1 -0
- package/dist/mail/service.d.ts +156 -0
- package/dist/mail/service.d.ts.map +1 -0
- package/dist/mail/service.js +265 -0
- package/dist/mail/service.js.map +1 -0
- package/dist/mail/smtp.d.ts +39 -0
- package/dist/mail/smtp.d.ts.map +1 -0
- package/dist/mail/smtp.js +58 -0
- package/dist/mail/smtp.js.map +1 -0
- package/dist/mail/templates.d.ts +91 -0
- package/dist/mail/templates.d.ts.map +1 -0
- package/dist/mail/templates.js +69 -0
- package/dist/mail/templates.js.map +1 -0
- package/dist/notifications/comments.d.ts +66 -0
- package/dist/notifications/comments.d.ts.map +1 -0
- package/dist/notifications/comments.js +151 -0
- package/dist/notifications/comments.js.map +1 -0
- package/dist/notifications/digest.d.ts +135 -0
- package/dist/notifications/digest.d.ts.map +1 -0
- package/dist/notifications/digest.js +287 -0
- package/dist/notifications/digest.js.map +1 -0
- package/dist/notifications/index.d.ts +13 -0
- package/dist/notifications/index.d.ts.map +1 -0
- package/dist/notifications/index.js +8 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/notifications/links.d.ts +60 -0
- package/dist/notifications/links.d.ts.map +1 -0
- package/dist/notifications/links.js +63 -0
- package/dist/notifications/links.js.map +1 -0
- package/dist/notifications/optouts.d.ts +48 -0
- package/dist/notifications/optouts.d.ts.map +1 -0
- package/dist/notifications/optouts.js +93 -0
- package/dist/notifications/optouts.js.map +1 -0
- package/dist/notifications/preferences.d.ts +158 -0
- package/dist/notifications/preferences.d.ts.map +1 -0
- package/dist/notifications/preferences.js +195 -0
- package/dist/notifications/preferences.js.map +1 -0
- package/dist/notifications/routes.d.ts +28 -0
- package/dist/notifications/routes.d.ts.map +1 -0
- package/dist/notifications/routes.js +184 -0
- package/dist/notifications/routes.js.map +1 -0
- package/dist/notifications/tokens.d.ts +60 -0
- package/dist/notifications/tokens.d.ts.map +1 -0
- package/dist/notifications/tokens.js +125 -0
- package/dist/notifications/tokens.js.map +1 -0
- package/dist/notify.d.ts +84 -0
- package/dist/notify.d.ts.map +1 -0
- package/dist/notify.js +149 -0
- package/dist/notify.js.map +1 -0
- package/dist/web/archive.d.ts +49 -0
- package/dist/web/archive.d.ts.map +1 -0
- package/dist/web/archive.js +59 -0
- package/dist/web/archive.js.map +1 -0
- package/dist/web/assets.d.ts +6 -5
- package/dist/web/assets.d.ts.map +1 -1
- package/dist/web/assets.js +5 -7
- package/dist/web/assets.js.map +1 -1
- package/dist/web/authors.d.ts +143 -0
- package/dist/web/authors.d.ts.map +1 -0
- package/dist/web/authors.js +178 -0
- package/dist/web/authors.js.map +1 -0
- package/dist/web/comments.d.ts +60 -0
- package/dist/web/comments.d.ts.map +1 -0
- package/dist/web/comments.js +207 -0
- package/dist/web/comments.js.map +1 -0
- package/dist/web/context.d.ts +162 -28
- package/dist/web/context.d.ts.map +1 -1
- package/dist/web/context.js +123 -26
- package/dist/web/context.js.map +1 -1
- package/dist/web/conversation.d.ts +209 -0
- package/dist/web/conversation.d.ts.map +1 -0
- package/dist/web/conversation.js +557 -0
- package/dist/web/conversation.js.map +1 -0
- package/dist/web/documents.d.ts +36 -6
- package/dist/web/documents.d.ts.map +1 -1
- package/dist/web/documents.js +52 -10
- package/dist/web/documents.js.map +1 -1
- package/dist/web/feed-atom.d.ts +22 -0
- package/dist/web/feed-atom.d.ts.map +1 -0
- package/dist/web/feed-atom.js +83 -0
- package/dist/web/feed-atom.js.map +1 -0
- package/dist/web/feed-item.d.ts +145 -0
- package/dist/web/feed-item.d.ts.map +1 -0
- package/dist/web/feed-item.js +120 -0
- package/dist/web/feed-item.js.map +1 -0
- package/dist/web/feed-json.d.ts +79 -0
- package/dist/web/feed-json.d.ts.map +1 -0
- package/dist/web/feed-json.js +53 -0
- package/dist/web/feed-json.js.map +1 -0
- package/dist/web/feed-rss.d.ts +26 -0
- package/dist/web/feed-rss.d.ts.map +1 -0
- package/dist/web/feed-rss.js +203 -0
- package/dist/web/feed-rss.js.map +1 -0
- package/dist/web/feed-source.d.ts +244 -0
- package/dist/web/feed-source.d.ts.map +1 -0
- package/dist/web/feed-source.js +201 -0
- package/dist/web/feed-source.js.map +1 -0
- package/dist/web/feed-xml.d.ts +63 -0
- package/dist/web/feed-xml.d.ts.map +1 -0
- package/dist/web/feed-xml.js +116 -0
- package/dist/web/feed-xml.js.map +1 -0
- package/dist/web/feeds.d.ts +31 -96
- package/dist/web/feeds.d.ts.map +1 -1
- package/dist/web/feeds.js +94 -198
- package/dist/web/feeds.js.map +1 -1
- package/dist/web/health.d.ts +31 -0
- package/dist/web/health.d.ts.map +1 -0
- package/dist/web/health.js +44 -0
- package/dist/web/health.js.map +1 -0
- package/dist/web/index.d.ts +30 -10
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +18 -7
- package/dist/web/index.js.map +1 -1
- package/dist/web/navigation.d.ts +73 -0
- package/dist/web/navigation.d.ts.map +1 -0
- package/dist/web/navigation.js +110 -0
- package/dist/web/navigation.js.map +1 -0
- package/dist/web/negotiate.d.ts +9 -0
- package/dist/web/negotiate.d.ts.map +1 -1
- package/dist/web/negotiate.js +6 -1
- package/dist/web/negotiate.js.map +1 -1
- package/dist/web/recent.d.ts +43 -0
- package/dist/web/recent.d.ts.map +1 -0
- package/dist/web/recent.js +37 -0
- package/dist/web/recent.js.map +1 -0
- package/dist/web/render.d.ts +197 -9
- package/dist/web/render.d.ts.map +1 -1
- package/dist/web/render.js +334 -17
- package/dist/web/render.js.map +1 -1
- package/dist/web/routes.d.ts +25 -8
- package/dist/web/routes.d.ts.map +1 -1
- package/dist/web/routes.js +845 -93
- package/dist/web/routes.js.map +1 -1
- package/dist/web/sanitize.d.ts +19 -0
- package/dist/web/sanitize.d.ts.map +1 -0
- package/dist/web/sanitize.js +272 -0
- package/dist/web/sanitize.js.map +1 -0
- package/dist/web/search.d.ts +79 -0
- package/dist/web/search.d.ts.map +1 -0
- package/dist/web/search.js +94 -0
- package/dist/web/search.js.map +1 -0
- package/dist/web/sitemap.d.ts +120 -0
- package/dist/web/sitemap.d.ts.map +1 -0
- package/dist/web/sitemap.js +199 -0
- package/dist/web/sitemap.js.map +1 -0
- package/dist/web/taxonomy.d.ts +152 -0
- package/dist/web/taxonomy.d.ts.map +1 -0
- package/dist/web/taxonomy.js +237 -0
- package/dist/web/taxonomy.js.map +1 -0
- package/dist/web/templates.d.ts +54 -16
- package/dist/web/templates.d.ts.map +1 -1
- package/dist/web/templates.js +109 -28
- package/dist/web/templates.js.map +1 -1
- package/dist/web/themes.d.ts +205 -0
- package/dist/web/themes.d.ts.map +1 -0
- package/dist/web/themes.js +277 -0
- package/dist/web/themes.js.map +1 -0
- package/dist/webmention/discovery.d.ts +51 -0
- package/dist/webmention/discovery.d.ts.map +1 -0
- package/dist/webmention/discovery.js +176 -0
- package/dist/webmention/discovery.js.map +1 -0
- package/dist/webmention/html.d.ts +71 -0
- package/dist/webmention/html.d.ts.map +1 -0
- package/dist/webmention/html.js +285 -0
- package/dist/webmention/html.js.map +1 -0
- package/dist/webmention/index.d.ts +12 -0
- package/dist/webmention/index.d.ts.map +1 -0
- package/dist/webmention/index.js +8 -0
- package/dist/webmention/index.js.map +1 -0
- package/dist/webmention/links.d.ts +18 -0
- package/dist/webmention/links.d.ts.map +1 -0
- package/dist/webmention/links.js +64 -0
- package/dist/webmention/links.js.map +1 -0
- package/dist/webmention/microformats.d.ts +93 -0
- package/dist/webmention/microformats.d.ts.map +1 -0
- package/dist/webmention/microformats.js +376 -0
- package/dist/webmention/microformats.js.map +1 -0
- package/dist/webmention/receive.d.ts +141 -0
- package/dist/webmention/receive.d.ts.map +1 -0
- package/dist/webmention/receive.js +264 -0
- package/dist/webmention/receive.js.map +1 -0
- package/dist/webmention/routes.d.ts +37 -0
- package/dist/webmention/routes.d.ts.map +1 -0
- package/dist/webmention/routes.js +75 -0
- package/dist/webmention/routes.js.map +1 -0
- package/dist/webmention/service.d.ts +94 -0
- package/dist/webmention/service.d.ts.map +1 -0
- package/dist/webmention/service.js +207 -0
- package/dist/webmention/service.js.map +1 -0
- package/package.json +11 -6
- package/templates/site/content/_data/site.json +1 -0
- package/templates/site/content/posts/2026-01-01-hello-world.md +5 -3
- package/templates/site/geekity.config.ts +11 -1
- package/templates/site/tsconfig.json +1 -1
- package/themes/default/README.md +1096 -50
- package/themes/default/layouts/404.njk +2 -3
- package/themes/default/layouts/author.njk +37 -0
- package/themes/default/layouts/base.njk +172 -31
- package/themes/default/layouts/category.njk +27 -0
- package/themes/default/layouts/front-page.njk +57 -0
- package/themes/default/layouts/home.njk +17 -1
- package/themes/default/layouts/page.njk +57 -5
- package/themes/default/layouts/post.njk +78 -19
- package/themes/default/layouts/search.njk +57 -0
- package/themes/default/layouts/tag.njk +3 -6
- package/themes/default/mail/comment-digest.html.njk +37 -0
- package/themes/default/mail/comment-digest.subject.njk +3 -0
- package/themes/default/mail/comment-digest.txt.njk +40 -0
- package/themes/default/mail/comment-pending.html.njk +27 -0
- package/themes/default/mail/comment-pending.subject.njk +2 -0
- package/themes/default/mail/comment-pending.txt.njk +24 -0
- package/themes/default/mail/comment-reply.html.njk +21 -0
- package/themes/default/mail/comment-reply.subject.njk +2 -0
- package/themes/default/mail/comment-reply.txt.njk +22 -0
- package/themes/default/mail/contact-message.html.njk +24 -0
- package/themes/default/mail/contact-message.subject.njk +5 -0
- package/themes/default/mail/contact-message.txt.njk +20 -0
- package/themes/default/mail/password-changed.html.njk +18 -0
- package/themes/default/mail/password-changed.subject.njk +2 -0
- package/themes/default/mail/password-changed.txt.njk +17 -0
- package/themes/default/mail/password-reset.html.njk +22 -0
- package/themes/default/mail/password-reset.subject.njk +2 -0
- package/themes/default/mail/password-reset.txt.njk +18 -0
- package/themes/default/mail/test.html.njk +14 -0
- package/themes/default/mail/test.subject.njk +2 -0
- package/themes/default/mail/test.txt.njk +12 -0
- package/themes/default/partials/archive.njk +27 -0
- package/themes/default/partials/bio.njk +77 -0
- package/themes/default/partials/comment-form.njk +109 -0
- package/themes/default/partials/contact-form.njk +88 -0
- package/themes/default/partials/conversation.njk +139 -0
- package/themes/default/partials/feeds.njk +26 -0
- package/themes/default/partials/jsonld.njk +120 -0
- package/themes/default/partials/pagination.njk +12 -6
- package/themes/default/partials/post-list.njk +51 -18
- package/themes/default/partials/search-form.njk +17 -0
- package/themes/default/partials/tags.njk +26 -5
- package/themes/default/static/highlight.js +4 -0
- package/themes/default/static/style.css +1119 -194
- package/themes/default/theme.json +5 -0
- package/admin/layouts/document-editor.njk +0 -167
- package/admin/layouts/federation.njk +0 -145
- package/admin/layouts/login.njk +0 -23
- package/admin/layouts/settings.njk +0 -116
- package/admin/layouts/setup.njk +0 -27
- package/admin/layouts/users.njk +0 -91
- /package/admin/{layouts/_flash.njk → components/flash.njk} +0 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { countContactMessagesByStatus, countUnreadContactMessages, deleteContactMessage, listContactMessages, setContactMessageRead, } from '../contact/records.js';
|
|
2
|
+
import { flash } from './flash.js';
|
|
3
|
+
import { formatInTimezone } from './formatting.js';
|
|
4
|
+
import { ADMIN_PREFIX } from './session.js';
|
|
5
|
+
import { readSiteSettings } from './settings.js';
|
|
6
|
+
import { ADMIN_TEMPLATES } from './templates.js';
|
|
7
|
+
/**
|
|
8
|
+
* The inbox: what the contact form left behind (TASK-56).
|
|
9
|
+
*
|
|
10
|
+
* A message is a file under `data/contact/` and nothing else — there is no
|
|
11
|
+
* index, because this screen reads every file to sort them anyway and a second
|
|
12
|
+
* copy of the truth would only be a second thing to keep true. So the two
|
|
13
|
+
* actions here are a rewrite and an unlink, and the screen is a view of the
|
|
14
|
+
* directory as it is at that moment (decision-9).
|
|
15
|
+
*
|
|
16
|
+
* Two lists rather than one. A checker's `spam` is an opinion about a message
|
|
17
|
+
* somebody may really have sent, so it is kept rather than thrown away — but a
|
|
18
|
+
* run of it must not bury the messages a person actually has to answer.
|
|
19
|
+
*/
|
|
20
|
+
/** Where the Messages screen lives. */
|
|
21
|
+
export const MESSAGES_PATH = `${ADMIN_PREFIX}/messages`;
|
|
22
|
+
/** The navigation section it marks as current. */
|
|
23
|
+
export const MESSAGES_SECTION = 'messages';
|
|
24
|
+
/** Where the Mark read and Mark unread buttons post. */
|
|
25
|
+
export const MESSAGES_READ_PATH = `${MESSAGES_PATH}/read`;
|
|
26
|
+
/** Where the Delete button posts. */
|
|
27
|
+
export const MESSAGES_DELETE_PATH = `${MESSAGES_PATH}/delete`;
|
|
28
|
+
/** The fields the forms on this screen submit. */
|
|
29
|
+
export const MESSAGE_FIELDS = {
|
|
30
|
+
/** Which message a button is about. */
|
|
31
|
+
id: 'id',
|
|
32
|
+
/** Whether Mark read means read or unread. Empty is unread. */
|
|
33
|
+
read: 'read',
|
|
34
|
+
/** Which list to go back to afterwards. */
|
|
35
|
+
status: 'status',
|
|
36
|
+
};
|
|
37
|
+
/** How many messages one page of a list shows. */
|
|
38
|
+
export const MESSAGES_PER_PAGE = 25;
|
|
39
|
+
/** The two lists, in the order the screen offers them. */
|
|
40
|
+
export const MESSAGE_TABS = [
|
|
41
|
+
{ status: 'received', label: 'Inbox' },
|
|
42
|
+
{ status: 'spam', label: 'Spam' },
|
|
43
|
+
];
|
|
44
|
+
/** Register the Messages screen. */
|
|
45
|
+
export function mountMessagesScreen(app, options) {
|
|
46
|
+
const { render } = options;
|
|
47
|
+
app.get(MESSAGES_PATH, (c) => {
|
|
48
|
+
const { dataDir, contentDir } = c.var.config;
|
|
49
|
+
const status = statusOf(c.req.query('status'));
|
|
50
|
+
const page = Math.max(1, Math.trunc(Number(c.req.query('page') ?? '1')) || 1);
|
|
51
|
+
const counts = countContactMessagesByStatus(dataDir);
|
|
52
|
+
const timezone = readSiteSettings(contentDir).timezone;
|
|
53
|
+
const rows = listContactMessages(dataDir, { status })
|
|
54
|
+
.slice((page - 1) * MESSAGES_PER_PAGE, page * MESSAGES_PER_PAGE)
|
|
55
|
+
.map((message) => messageRow(message, timezone));
|
|
56
|
+
const pages = Math.max(1, Math.ceil(counts[status] / MESSAGES_PER_PAGE));
|
|
57
|
+
return render(c, ADMIN_TEMPLATES.messages, {
|
|
58
|
+
section: MESSAGES_SECTION,
|
|
59
|
+
child: 'all',
|
|
60
|
+
heading: 'Messages',
|
|
61
|
+
status,
|
|
62
|
+
counts,
|
|
63
|
+
unread: countUnreadContactMessages(dataDir),
|
|
64
|
+
tabs: MESSAGE_TABS.map((tab) => ({
|
|
65
|
+
...tab,
|
|
66
|
+
url: listUrl(tab.status),
|
|
67
|
+
count: counts[tab.status],
|
|
68
|
+
current: tab.status === status,
|
|
69
|
+
})),
|
|
70
|
+
rows,
|
|
71
|
+
fields: MESSAGE_FIELDS,
|
|
72
|
+
readUrl: MESSAGES_READ_PATH,
|
|
73
|
+
deleteUrl: MESSAGES_DELETE_PATH,
|
|
74
|
+
page,
|
|
75
|
+
pages,
|
|
76
|
+
previousUrl: page > 1 ? listUrl(status, page - 1) : undefined,
|
|
77
|
+
nextUrl: page < pages ? listUrl(status, page + 1) : undefined,
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
app.post(MESSAGES_READ_PATH, async (c) => {
|
|
81
|
+
const body = await c.req.parseBody();
|
|
82
|
+
const back = listUrl(statusOf(text(body[MESSAGE_FIELDS.status])));
|
|
83
|
+
// A checkbox submits nothing at all when it is clear, which is what the
|
|
84
|
+
// empty string here means: this button marks the message unread again.
|
|
85
|
+
const read = text(body[MESSAGE_FIELDS.read]) !== '';
|
|
86
|
+
const updated = await setContactMessageRead(c.var.config.dataDir, text(body[MESSAGE_FIELDS.id]), read);
|
|
87
|
+
if (updated === undefined) {
|
|
88
|
+
flash(c, 'error', 'That message is not here any more.');
|
|
89
|
+
return c.redirect(back, 303);
|
|
90
|
+
}
|
|
91
|
+
flash(c, 'notice', read
|
|
92
|
+
? `Marked ${updated.from.name}’s message read.`
|
|
93
|
+
: `Marked ${updated.from.name}’s message unread.`);
|
|
94
|
+
return c.redirect(back, 303);
|
|
95
|
+
});
|
|
96
|
+
app.post(MESSAGES_DELETE_PATH, async (c) => {
|
|
97
|
+
const body = await c.req.parseBody();
|
|
98
|
+
const back = listUrl(statusOf(text(body[MESSAGE_FIELDS.status])));
|
|
99
|
+
const id = text(body[MESSAGE_FIELDS.id]);
|
|
100
|
+
// Read before it goes, so the flash can say whose message it was. An id
|
|
101
|
+
// that is not one never reaches the filesystem: the records module refuses
|
|
102
|
+
// anything but the shape it mints.
|
|
103
|
+
const held = listContactMessages(c.var.config.dataDir).find((message) => message.id === id)
|
|
104
|
+
?.from.name;
|
|
105
|
+
if (!(await deleteContactMessage(c.var.config.dataDir, id))) {
|
|
106
|
+
flash(c, 'error', 'That message is not here any more.');
|
|
107
|
+
return c.redirect(back, 303);
|
|
108
|
+
}
|
|
109
|
+
flash(c, 'notice', `Deleted ${held ?? 'the'} message.`);
|
|
110
|
+
return c.redirect(back, 303);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/** How many messages are waiting to be read, which is the number on the dashboard. */
|
|
114
|
+
export function unreadMessages(dataDir) {
|
|
115
|
+
return countUnreadContactMessages(dataDir);
|
|
116
|
+
}
|
|
117
|
+
/** Where one list of the inbox lives. */
|
|
118
|
+
export function listUrl(status, page = 1) {
|
|
119
|
+
const query = page > 1 ? `&page=${String(page)}` : '';
|
|
120
|
+
return `${MESSAGES_PATH}?status=${status}${query}`;
|
|
121
|
+
}
|
|
122
|
+
/** A requested status, or the inbox for anything else. */
|
|
123
|
+
function statusOf(value) {
|
|
124
|
+
return value === 'spam' ? 'spam' : 'received';
|
|
125
|
+
}
|
|
126
|
+
/** One stored message as the screen shows it. */
|
|
127
|
+
function messageRow(message, timezone) {
|
|
128
|
+
const subject = message.subject === '' ? `A message from ${message.from.name}` : message.subject;
|
|
129
|
+
return {
|
|
130
|
+
id: message.id,
|
|
131
|
+
status: message.status,
|
|
132
|
+
read: message.read,
|
|
133
|
+
author: message.from.name,
|
|
134
|
+
email: message.from.email,
|
|
135
|
+
subject: message.subject,
|
|
136
|
+
message: message.message,
|
|
137
|
+
received: formatInTimezone(message.received, timezone),
|
|
138
|
+
// The first eight characters are enough to see that two messages came from
|
|
139
|
+
// one place, which is the only question the hash is there to answer.
|
|
140
|
+
address: message.addressHash === null ? null : message.addressHash.slice(0, 8),
|
|
141
|
+
page: message.page.title === '' ? message.page.slug : message.page.title,
|
|
142
|
+
pageUrl: message.page.permalink === '' ? null : message.page.permalink,
|
|
143
|
+
// Answering from here rather than from a mail client is one click; the
|
|
144
|
+
// subject comes back with `Re:` on it the way a reply would.
|
|
145
|
+
replyUrl: `mailto:${encodeURIComponent(message.from.email)}?subject=${encodeURIComponent(`Re: ${subject}`)}`,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/** A form field as a string. A file upload, or a missing field, is the empty one. */
|
|
149
|
+
function text(value) {
|
|
150
|
+
return typeof value === 'string' ? value : '';
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=messages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messages.js","sourceRoot":"","sources":["../../src/admin/messages.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/admin/preview.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/admin/preview.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,MAAM,CAAC;AAO1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAS5C,8DAA8D;AAC9D,eAAO,MAAM,YAAY,mBAA4B,CAAC;AAEtD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,CAqBxD"}
|
package/dist/admin/preview.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { renderMarkdown } from '../content/markdown.js';
|
|
2
2
|
import { defaultPermalink, slugify } from '../content/slug.js';
|
|
3
|
+
import { calendarDayIn, toUtcInstant } from '../content/time.js';
|
|
3
4
|
import { normalizeBody } from '../content/writer.js';
|
|
5
|
+
import { authorContext } from '../web/authors.js';
|
|
4
6
|
import { documentContext } from '../web/context.js';
|
|
7
|
+
import { findUserById, listUsers } from './accounts.js';
|
|
5
8
|
import { TEMPLATES } from '../web/render.js';
|
|
6
9
|
import { splitTags } from './documents.js';
|
|
10
|
+
import { readSiteSettings } from './settings.js';
|
|
7
11
|
import { ADMIN_PREFIX } from './session.js';
|
|
8
12
|
/** Where the editor posts a body it wants to see rendered. */
|
|
9
13
|
export const PREVIEW_PATH = `${ADMIN_PREFIX}/preview`;
|
|
@@ -23,14 +27,24 @@ export function mountPreview(app) {
|
|
|
23
27
|
app.post(PREVIEW_PATH, async (c) => {
|
|
24
28
|
const body = await c.req.parseBody();
|
|
25
29
|
const type = text(body['type']) === 'page' ? 'page' : 'post';
|
|
26
|
-
|
|
30
|
+
const document = previewDocument(c, { type, body });
|
|
31
|
+
return c.html(c.var.renderer.render(type === 'post' ? TEMPLATES.post : TEMPLATES.page,
|
|
32
|
+
// With the site's image config, so a preview shows the `<picture>` the
|
|
33
|
+
// published page would show rather than the plain image behind it, and
|
|
34
|
+
// with the author resolved, so the byline reads the way the published
|
|
35
|
+
// page's would (TASK-67).
|
|
36
|
+
documentContext(document, c.var.config, authorContext(listUsers(c.var.config.dataDir), document.author))));
|
|
27
37
|
});
|
|
28
38
|
}
|
|
29
39
|
/** What a submitted editor form looks like as a document nobody has saved. */
|
|
30
40
|
function previewDocument(c, input) {
|
|
31
41
|
const { type, body } = input;
|
|
42
|
+
const timezone = readSiteSettings(c.var.config.contentDir).timezone;
|
|
32
43
|
const title = text(body['title']).trim();
|
|
33
|
-
|
|
44
|
+
// The editor's field is wall-clock time in the site's zone (decision-11), so
|
|
45
|
+
// the preview reads it the way the save will and shows the date the saved
|
|
46
|
+
// post would show rather than one the server's own zone invented.
|
|
47
|
+
const date = type === 'post' ? orNow(text(body['date']).trim(), timezone, c.var.store.now()) : undefined;
|
|
34
48
|
const slug = slugify(text(body['slug']).trim()) || slugify(title) || 'preview';
|
|
35
49
|
const markdown = normalizeBody(text(body['body']));
|
|
36
50
|
return {
|
|
@@ -39,10 +53,16 @@ function previewDocument(c, input) {
|
|
|
39
53
|
// prints them; neither is ever written anywhere.
|
|
40
54
|
path: `${type === 'post' ? 'posts' : 'pages'}/${slug}.md`,
|
|
41
55
|
slug,
|
|
42
|
-
permalink: permalinkFor({
|
|
56
|
+
permalink: permalinkFor({
|
|
57
|
+
type,
|
|
58
|
+
slug,
|
|
59
|
+
date: date === undefined ? undefined : (calendarDayIn(date, timezone) ?? date),
|
|
60
|
+
submitted: text(body['permalink']).trim(),
|
|
61
|
+
}),
|
|
43
62
|
title: title === '' ? 'Untitled' : title,
|
|
44
63
|
...(date === undefined ? {} : { date }),
|
|
45
64
|
tags: splitTags(text(body['tags'])),
|
|
65
|
+
categories: splitTags(text(body['categories'])),
|
|
46
66
|
draft: body['draft'] !== undefined,
|
|
47
67
|
...(text(body['description']) === '' ? {} : { description: text(body['description']).trim() }),
|
|
48
68
|
...(currentUsername(c) === undefined ? {} : { author: currentUsername(c) }),
|
|
@@ -65,16 +85,20 @@ function permalinkFor(input) {
|
|
|
65
85
|
return '/';
|
|
66
86
|
}
|
|
67
87
|
}
|
|
68
|
-
/**
|
|
69
|
-
|
|
70
|
-
|
|
88
|
+
/**
|
|
89
|
+
* The date field as a UTC instant. One that is empty or unusable becomes now,
|
|
90
|
+
* so the layout always has one to print.
|
|
91
|
+
*/
|
|
92
|
+
function orNow(value, timezone, now) {
|
|
93
|
+
const instant = /^\d{4}-\d{2}-\d{2}/.test(value) ? toUtcInstant(value, timezone) : undefined;
|
|
94
|
+
return instant ?? now.toISOString();
|
|
71
95
|
}
|
|
72
96
|
/** The signed-in user's login, so a byline in the theme has something to print. */
|
|
73
97
|
function currentUsername(c) {
|
|
74
98
|
const userId = c.var.session?.userId;
|
|
75
99
|
if (userId == null)
|
|
76
100
|
return undefined;
|
|
77
|
-
return c.var.
|
|
101
|
+
return findUserById(c.var.config.dataDir, userId)?.username;
|
|
78
102
|
}
|
|
79
103
|
/** A form field as a string. A file upload, or a missing field, is the empty one. */
|
|
80
104
|
function text(value) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/admin/preview.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,8DAA8D;AAC9D,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,YAAY,UAAU,CAAC;AAEtD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAAC,GAAqB;IAChD,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACjC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;QACrC,MAAM,IAAI,GAAiB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"preview.js","sourceRoot":"","sources":["../../src/admin/preview.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Context, Hono } from 'hono';
|
|
2
|
+
import type { ResolvedConfig } from '../config.ts';
|
|
3
|
+
import type { GeekityEnv } from '../env.ts';
|
|
4
|
+
import type { AdminRender } from './documents.ts';
|
|
5
|
+
import type { Session } from './store.ts';
|
|
6
|
+
import type { LoginThrottle } from './throttle.ts';
|
|
7
|
+
/**
|
|
8
|
+
* Forgetting a password, and getting back in without a shell.
|
|
9
|
+
*
|
|
10
|
+
* Two unauthenticated screens, built to the OWASP Forgot Password guidance,
|
|
11
|
+
* and four rules that between them are the whole design:
|
|
12
|
+
*
|
|
13
|
+
* - **One answer.** The request form says exactly the same thing to a name
|
|
14
|
+
* that exists, a name that does not, and a name whose user has no email
|
|
15
|
+
* address. A recovery form that answered differently would be a list of
|
|
16
|
+
* which accounts a site has, handed to anybody who asked.
|
|
17
|
+
* - **The link is the secret.** 256 random bits, in the URL, sent to an
|
|
18
|
+
* address somebody put on their own account. The database keeps only its
|
|
19
|
+
* SHA-256 (see the `password_resets` migration), so a copy of the cache is
|
|
20
|
+
* not a stack of working links, and the token is never rendered into the
|
|
21
|
+
* page of the browser that asked for it — only into the message.
|
|
22
|
+
* - **It is spent when it is used.** Setting a password deletes every reset
|
|
23
|
+
* that user had outstanding and ends every session they had, because the
|
|
24
|
+
* ordinary reason to reset a password is that somebody else may have had it.
|
|
25
|
+
* - **It cannot be a lever.** Requests are rate limited exactly as sign-ins
|
|
26
|
+
* are (TASK-47), on a throttle of their own, so a flood of recovery requests
|
|
27
|
+
* for one username cannot lock that person out of logging in.
|
|
28
|
+
*/
|
|
29
|
+
/** Where the request-a-link form lives. */
|
|
30
|
+
export declare const FORGOT_PATH = "/admin/forgot";
|
|
31
|
+
/** Where the set-a-new-password form lives. The link points here. */
|
|
32
|
+
export declare const RESET_PATH = "/admin/reset";
|
|
33
|
+
/** The fields the two forms submit. */
|
|
34
|
+
export declare const RECOVERY_FIELDS: {
|
|
35
|
+
/** A username or an email address; the form does not ask which. */
|
|
36
|
+
readonly identifier: 'identifier';
|
|
37
|
+
/** The token, carried from the link's query string into the form. */
|
|
38
|
+
readonly token: 'token';
|
|
39
|
+
readonly password: 'password';
|
|
40
|
+
readonly passwordConfirmation: 'password_confirmation';
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* How long a link lasts, in seconds.
|
|
44
|
+
*
|
|
45
|
+
* An hour, which is what the task asks for and what OWASP calls a reasonable
|
|
46
|
+
* upper bound: long enough to walk to another machine and read the mail there,
|
|
47
|
+
* short enough that a link left in an inbox is not a spare key.
|
|
48
|
+
*/
|
|
49
|
+
export declare const RESET_TOKEN_LIFETIME_SECONDS = 3600;
|
|
50
|
+
/** The message asking somebody whether they meant to do this. */
|
|
51
|
+
export declare const RESET_TEMPLATE = "password-reset";
|
|
52
|
+
/** The message telling them it happened. */
|
|
53
|
+
export declare const PASSWORD_CHANGED_TEMPLATE = "password-changed";
|
|
54
|
+
/**
|
|
55
|
+
* The one thing the request form ever says.
|
|
56
|
+
*
|
|
57
|
+
* Spelled once, and rendered whatever happened, so the two paths through the
|
|
58
|
+
* handler cannot drift into two different answers.
|
|
59
|
+
*/
|
|
60
|
+
export declare const RECOVERY_ANSWER: string;
|
|
61
|
+
/** What {@link mountRecovery} needs from the admin around it. */
|
|
62
|
+
export interface MountRecoveryOptions {
|
|
63
|
+
/** The admin's renderer, which injects the chrome, the CSRF token and the flash. */
|
|
64
|
+
render: AdminRender;
|
|
65
|
+
/**
|
|
66
|
+
* A session to hang a CSRF token on, made on the spot if the visitor has
|
|
67
|
+
* none. The same one the login and setup forms use: these pages are reached
|
|
68
|
+
* by somebody with no login, and a form with no session has no token.
|
|
69
|
+
*/
|
|
70
|
+
anonymousSession: (c: Context<GeekityEnv>) => Session;
|
|
71
|
+
/**
|
|
72
|
+
* The throttle recovery requests are counted against.
|
|
73
|
+
*
|
|
74
|
+
* Its own, not the login one. Sharing would mean a stranger could lock
|
|
75
|
+
* somebody out of signing in by asking for their password to be reset over
|
|
76
|
+
* and over — turning a recovery feature into a denial of service against the
|
|
77
|
+
* person it exists to help.
|
|
78
|
+
*/
|
|
79
|
+
throttle: (config: ResolvedConfig) => LoginThrottle;
|
|
80
|
+
}
|
|
81
|
+
/** Register the forgot-password and reset screens. */
|
|
82
|
+
export declare function mountRecovery(app: Hono<GeekityEnv>, options: MountRecoveryOptions): void;
|
|
83
|
+
/**
|
|
84
|
+
* What is wrong with the password somebody typed into the reset form.
|
|
85
|
+
*
|
|
86
|
+
* The same rules the change-password form and `geekity user add` enforce, so a
|
|
87
|
+
* password set through a reset link is one every other door would have taken.
|
|
88
|
+
*/
|
|
89
|
+
export declare function newPasswordProblem(password: string, confirmation: string): string | undefined;
|
|
90
|
+
/** The link that goes in the message, absolute because it is read elsewhere. */
|
|
91
|
+
export declare function resetLink(baseUrl: string, token: string): string;
|
|
92
|
+
//# sourceMappingURL=recovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery.d.ts","sourceRoot":"","sources":["../../src/admin/recovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAG5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGlD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAG1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,2CAA2C;AAC3C,eAAO,MAAM,WAAW,kBAA2B,CAAC;AAEpD,qEAAqE;AACrE,eAAO,MAAM,UAAU,iBAA0B,CAAC;AAElD,uCAAuC;AACvC,eAAO,MAAM,eAAe;IAC1B,mEAAmE;aACnE,UAAU,EAAE,YAAY;IACxB,qEAAqE;aACrE,KAAK,EAAE,OAAO;aACd,QAAQ,EAAE,UAAU;aACpB,oBAAoB,EAAE,uBAAuB;CACrC,CAAC;AAEX;;;;;;GAMG;AACH,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAEjD,iEAAiE;AACjE,eAAO,MAAM,cAAc,mBAAmB,CAAC;AAE/C,4CAA4C;AAC5C,eAAO,MAAM,yBAAyB,qBAAqB,CAAC;AAE5D;;;;;GAKG;AACH,eAAO,MAAM,eAAe,QAEoE,CAAC;AAEjG,iEAAiE;AACjE,MAAM,WAAW,oBAAoB;IACnC,oFAAoF;IACpF,MAAM,EAAE,WAAW,CAAC;IACpB;;;;OAIG;IACH,gBAAgB,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,OAAO,CAAC;IACtD;;;;;;;OAOG;IACH,QAAQ,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,aAAa,CAAC;CACrD;AAED,sDAAsD;AACtD,wBAAgB,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,oBAAoB,GAAG,IAAI,CA6JxF;AAiBD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK7F;AAED,gFAAgF;AAChF,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAIhE"}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { findUserById, findUserByIdentifier, setUserPassword } from './accounts.js';
|
|
2
|
+
import { passwordProblem } from './credentials.js';
|
|
3
|
+
import { flash } from './flash.js';
|
|
4
|
+
import { ADMIN_PREFIX } from './session.js';
|
|
5
|
+
import { ADMIN_TEMPLATES } from './templates.js';
|
|
6
|
+
import { clientAddress, describeWait, loginKeys } from './throttle.js';
|
|
7
|
+
/**
|
|
8
|
+
* Forgetting a password, and getting back in without a shell.
|
|
9
|
+
*
|
|
10
|
+
* Two unauthenticated screens, built to the OWASP Forgot Password guidance,
|
|
11
|
+
* and four rules that between them are the whole design:
|
|
12
|
+
*
|
|
13
|
+
* - **One answer.** The request form says exactly the same thing to a name
|
|
14
|
+
* that exists, a name that does not, and a name whose user has no email
|
|
15
|
+
* address. A recovery form that answered differently would be a list of
|
|
16
|
+
* which accounts a site has, handed to anybody who asked.
|
|
17
|
+
* - **The link is the secret.** 256 random bits, in the URL, sent to an
|
|
18
|
+
* address somebody put on their own account. The database keeps only its
|
|
19
|
+
* SHA-256 (see the `password_resets` migration), so a copy of the cache is
|
|
20
|
+
* not a stack of working links, and the token is never rendered into the
|
|
21
|
+
* page of the browser that asked for it — only into the message.
|
|
22
|
+
* - **It is spent when it is used.** Setting a password deletes every reset
|
|
23
|
+
* that user had outstanding and ends every session they had, because the
|
|
24
|
+
* ordinary reason to reset a password is that somebody else may have had it.
|
|
25
|
+
* - **It cannot be a lever.** Requests are rate limited exactly as sign-ins
|
|
26
|
+
* are (TASK-47), on a throttle of their own, so a flood of recovery requests
|
|
27
|
+
* for one username cannot lock that person out of logging in.
|
|
28
|
+
*/
|
|
29
|
+
/** Where the request-a-link form lives. */
|
|
30
|
+
export const FORGOT_PATH = `${ADMIN_PREFIX}/forgot`;
|
|
31
|
+
/** Where the set-a-new-password form lives. The link points here. */
|
|
32
|
+
export const RESET_PATH = `${ADMIN_PREFIX}/reset`;
|
|
33
|
+
/** The fields the two forms submit. */
|
|
34
|
+
export const RECOVERY_FIELDS = {
|
|
35
|
+
/** A username or an email address; the form does not ask which. */
|
|
36
|
+
identifier: 'identifier',
|
|
37
|
+
/** The token, carried from the link's query string into the form. */
|
|
38
|
+
token: 'token',
|
|
39
|
+
password: 'password',
|
|
40
|
+
passwordConfirmation: 'password_confirmation',
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* How long a link lasts, in seconds.
|
|
44
|
+
*
|
|
45
|
+
* An hour, which is what the task asks for and what OWASP calls a reasonable
|
|
46
|
+
* upper bound: long enough to walk to another machine and read the mail there,
|
|
47
|
+
* short enough that a link left in an inbox is not a spare key.
|
|
48
|
+
*/
|
|
49
|
+
export const RESET_TOKEN_LIFETIME_SECONDS = 3600;
|
|
50
|
+
/** The message asking somebody whether they meant to do this. */
|
|
51
|
+
export const RESET_TEMPLATE = 'password-reset';
|
|
52
|
+
/** The message telling them it happened. */
|
|
53
|
+
export const PASSWORD_CHANGED_TEMPLATE = 'password-changed';
|
|
54
|
+
/**
|
|
55
|
+
* The one thing the request form ever says.
|
|
56
|
+
*
|
|
57
|
+
* Spelled once, and rendered whatever happened, so the two paths through the
|
|
58
|
+
* handler cannot drift into two different answers.
|
|
59
|
+
*/
|
|
60
|
+
export const RECOVERY_ANSWER = 'If that matches an account with an email address on it, a link to set a new password is on ' +
|
|
61
|
+
'its way. The link lasts an hour and works once. Check the spam folder if it does not arrive.';
|
|
62
|
+
/** Register the forgot-password and reset screens. */
|
|
63
|
+
export function mountRecovery(app, options) {
|
|
64
|
+
const { render, anonymousSession, throttle } = options;
|
|
65
|
+
app.get(FORGOT_PATH, (c) => {
|
|
66
|
+
anonymousSession(c);
|
|
67
|
+
return render(c, ADMIN_TEMPLATES.forgot, forgotScreen(c));
|
|
68
|
+
});
|
|
69
|
+
app.post(FORGOT_PATH, async (c) => {
|
|
70
|
+
const config = c.var.config;
|
|
71
|
+
const body = await c.req.parseBody();
|
|
72
|
+
const identifier = field(body[RECOVERY_FIELDS.identifier]).trim();
|
|
73
|
+
const address = clientAddress(c, config);
|
|
74
|
+
const keys = loginKeys(identifier, address);
|
|
75
|
+
const throttled = throttle(config);
|
|
76
|
+
const wait = throttled.retryAfter(keys);
|
|
77
|
+
if (wait !== undefined) {
|
|
78
|
+
anonymousSession(c);
|
|
79
|
+
c.status(429);
|
|
80
|
+
c.header('Retry-After', String(wait));
|
|
81
|
+
return render(c, ADMIN_TEMPLATES.forgot,
|
|
82
|
+
// Says nothing about whether that account exists: an unknown name is
|
|
83
|
+
// counted and locked out exactly as a real one is.
|
|
84
|
+
forgotScreen(c, { error: `Too many requests. Try again in ${describeWait(wait)}.` }));
|
|
85
|
+
}
|
|
86
|
+
// Every request is counted, not only the ones that matched: counting a
|
|
87
|
+
// match differently would make the lockout itself the answer the rest of
|
|
88
|
+
// this handler refuses to give.
|
|
89
|
+
throttled.fail(keys);
|
|
90
|
+
const user = identifier === '' ? undefined : findUserByIdentifier(config.dataDir, identifier);
|
|
91
|
+
if (user?.email !== undefined && c.var.mail.configured()) {
|
|
92
|
+
// A good moment to sweep: resets are rare, and this is the one handler
|
|
93
|
+
// that writes a row to the table.
|
|
94
|
+
c.var.admin.prunePasswordResets();
|
|
95
|
+
const reset = c.var.admin.createPasswordReset({
|
|
96
|
+
userId: user.id,
|
|
97
|
+
lifetimeSeconds: RESET_TOKEN_LIFETIME_SECONDS,
|
|
98
|
+
});
|
|
99
|
+
// Dropped rather than awaited, for two reasons: the answer is the same
|
|
100
|
+
// whatever the provider says, and waiting for a provider on one path and
|
|
101
|
+
// not the other is how the page's timing would start telling people
|
|
102
|
+
// which usernames exist.
|
|
103
|
+
void c.var.mail.send({
|
|
104
|
+
to: user.email,
|
|
105
|
+
template: RESET_TEMPLATE,
|
|
106
|
+
data: {
|
|
107
|
+
username: user.username,
|
|
108
|
+
resetUrl: resetLink(config.baseUrl, reset.token),
|
|
109
|
+
expiresAt: reset.expiresAt,
|
|
110
|
+
expiresInHours: RESET_TOKEN_LIFETIME_SECONDS / 3600,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
anonymousSession(c);
|
|
115
|
+
return render(c, ADMIN_TEMPLATES.forgot, forgotScreen(c, { sent: true }));
|
|
116
|
+
});
|
|
117
|
+
app.get(RESET_PATH, (c) => {
|
|
118
|
+
anonymousSession(c);
|
|
119
|
+
const token = c.req.query(RECOVERY_FIELDS.token) ?? '';
|
|
120
|
+
const reset = token === '' ? undefined : c.var.admin.getPasswordReset(token);
|
|
121
|
+
if (reset === undefined)
|
|
122
|
+
return refuseToken(c);
|
|
123
|
+
return render(c, ADMIN_TEMPLATES.reset, resetScreen(token));
|
|
124
|
+
});
|
|
125
|
+
app.post(RESET_PATH, async (c) => {
|
|
126
|
+
const dataDir = c.var.config.dataDir;
|
|
127
|
+
const body = await c.req.parseBody();
|
|
128
|
+
const token = field(body[RECOVERY_FIELDS.token]);
|
|
129
|
+
const password = field(body[RECOVERY_FIELDS.password]);
|
|
130
|
+
const confirmation = field(body[RECOVERY_FIELDS.passwordConfirmation]);
|
|
131
|
+
// Checked before the password rules, so a stale link is a stale link
|
|
132
|
+
// whatever was typed into it — and so a refused form never spends one.
|
|
133
|
+
const reset = token === '' ? undefined : c.var.admin.getPasswordReset(token);
|
|
134
|
+
if (reset === undefined)
|
|
135
|
+
return refuseToken(c);
|
|
136
|
+
const problem = newPasswordProblem(password, confirmation);
|
|
137
|
+
if (problem !== undefined) {
|
|
138
|
+
c.status(400);
|
|
139
|
+
// Nothing typed is echoed back: both fields are passwords.
|
|
140
|
+
return render(c, ADMIN_TEMPLATES.reset, resetScreen(token, { problem }));
|
|
141
|
+
}
|
|
142
|
+
const user = findUserById(dataDir, reset.userId);
|
|
143
|
+
if (user === undefined) {
|
|
144
|
+
// The account went while the link was in flight. The token is worthless
|
|
145
|
+
// now, and saying so beats a 500 from a write against nobody.
|
|
146
|
+
c.var.admin.deletePasswordResetsForUser(reset.userId);
|
|
147
|
+
return refuseToken(c);
|
|
148
|
+
}
|
|
149
|
+
await setUserPassword({ dataDir, userId: user.id, password });
|
|
150
|
+
// Everything that was a way into this account is now closed: the link that
|
|
151
|
+
// was just used, every other link asked for while it was in flight, and
|
|
152
|
+
// every browser that was signed in. Nobody is spared, because the person
|
|
153
|
+
// doing this is not signed in — they are about to be.
|
|
154
|
+
c.var.admin.deletePasswordResetsForUser(user.id);
|
|
155
|
+
const ended = c.var.admin.deleteSessionsForUser(user.id);
|
|
156
|
+
if (user.email !== undefined) {
|
|
157
|
+
// The other half of the OWASP guidance: whoever owns the address hears
|
|
158
|
+
// that the password changed, so a reset they did not ask for is
|
|
159
|
+
// something they find out about rather than something they discover.
|
|
160
|
+
void c.var.mail.send({
|
|
161
|
+
to: user.email,
|
|
162
|
+
template: PASSWORD_CHANGED_TEMPLATE,
|
|
163
|
+
data: { username: user.username, signedOut: ended },
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
flash(c, 'notice', 'Your password was changed. Sign in with the new one.');
|
|
167
|
+
return c.redirect(LOGIN_REDIRECT, 303);
|
|
168
|
+
});
|
|
169
|
+
/** What both handlers say to a token that buys nothing. */
|
|
170
|
+
function refuseToken(c) {
|
|
171
|
+
anonymousSession(c);
|
|
172
|
+
c.status(400);
|
|
173
|
+
return render(c, ADMIN_TEMPLATES.reset, {
|
|
174
|
+
forgotUrl: FORGOT_PATH,
|
|
175
|
+
resetUrl: RESET_PATH,
|
|
176
|
+
fields: RECOVERY_FIELDS,
|
|
177
|
+
invalid: true,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/** Everything the forgot-password template renders. */
|
|
181
|
+
function forgotScreen(c, extra = {}) {
|
|
182
|
+
return {
|
|
183
|
+
forgotUrl: FORGOT_PATH,
|
|
184
|
+
loginUrl: LOGIN_REDIRECT,
|
|
185
|
+
fields: RECOVERY_FIELDS,
|
|
186
|
+
// Read per request, exactly as the mail service reads it: a credential
|
|
187
|
+
// pasted into the settings screen turns recovery on for the next visitor
|
|
188
|
+
// without a restart.
|
|
189
|
+
configured: c.var.mail.configured(),
|
|
190
|
+
answer: RECOVERY_ANSWER,
|
|
191
|
+
sent: false,
|
|
192
|
+
...extra,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/** Where a finished reset sends the browser. Spelled here to avoid a cycle. */
|
|
197
|
+
const LOGIN_REDIRECT = `${ADMIN_PREFIX}/login`;
|
|
198
|
+
/** Everything the reset template renders for a link that is still good. */
|
|
199
|
+
function resetScreen(token, extra = {}) {
|
|
200
|
+
return {
|
|
201
|
+
forgotUrl: FORGOT_PATH,
|
|
202
|
+
resetUrl: RESET_PATH,
|
|
203
|
+
fields: RECOVERY_FIELDS,
|
|
204
|
+
token,
|
|
205
|
+
invalid: false,
|
|
206
|
+
...extra,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* What is wrong with the password somebody typed into the reset form.
|
|
211
|
+
*
|
|
212
|
+
* The same rules the change-password form and `geekity user add` enforce, so a
|
|
213
|
+
* password set through a reset link is one every other door would have taken.
|
|
214
|
+
*/
|
|
215
|
+
export function newPasswordProblem(password, confirmation) {
|
|
216
|
+
return (passwordProblem(password) ??
|
|
217
|
+
(password === confirmation ? undefined : 'The two passwords do not match.'));
|
|
218
|
+
}
|
|
219
|
+
/** The link that goes in the message, absolute because it is read elsewhere. */
|
|
220
|
+
export function resetLink(baseUrl, token) {
|
|
221
|
+
const url = new URL(RESET_PATH, baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`);
|
|
222
|
+
url.searchParams.set(RECOVERY_FIELDS.token, token);
|
|
223
|
+
return url.href;
|
|
224
|
+
}
|
|
225
|
+
/** A form field as a string. A file upload, or a missing field, is the empty one. */
|
|
226
|
+
function field(value) {
|
|
227
|
+
return typeof value === 'string' ? value : '';
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=recovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery.js","sourceRoot":"","sources":["../../src/admin/recovery.ts"],"names":[],"mappings":"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"}
|
package/dist/admin/routes.d.ts
CHANGED
|
@@ -6,26 +6,6 @@ export declare const LOGIN_PATH = "/admin/login";
|
|
|
6
6
|
export declare const SETUP_PATH = "/admin/setup";
|
|
7
7
|
/** Where the logout form posts. */
|
|
8
8
|
export declare const LOGOUT_PATH = "/admin/logout";
|
|
9
|
-
/** Where the dashboard's quick draft form posts. */
|
|
10
|
-
export declare const QUICK_DRAFT_PATH = "/admin/quick-draft";
|
|
11
|
-
/** One entry in the admin's left-hand navigation. */
|
|
12
|
-
export interface AdminSection {
|
|
13
|
-
/** The name a screen passes as `section` to mark itself current. */
|
|
14
|
-
section: string;
|
|
15
|
-
/** What the link says. */
|
|
16
|
-
label: string;
|
|
17
|
-
/** Where it goes. */
|
|
18
|
-
url: string;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* The sections of the admin, in the order doc-5 lists them.
|
|
22
|
-
*
|
|
23
|
-
* Every screen renders the same list and marks one of them, so a screen added
|
|
24
|
-
* later only has to name its section. The screens behind most of these links
|
|
25
|
-
* are still placeholders; the navigation is the shape of the whole admin, not
|
|
26
|
-
* of the part that is built.
|
|
27
|
-
*/
|
|
28
|
-
export declare const ADMIN_SECTIONS: readonly AdminSection[];
|
|
29
9
|
/** How many recent posts the dashboard lists. */
|
|
30
10
|
export declare const DASHBOARD_RECENT_POSTS = 5;
|
|
31
11
|
/** Where the editor for a post lives. */
|