@geekity/cms 0.15.0 → 0.17.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 +108 -0
- package/admin/pages/documents/editor.njk +51 -0
- package/admin/pages/documents/syndication.njk +110 -0
- package/admin/pages/federation/followers.njk +7 -0
- package/admin/pages/users/activity-entry.njk +58 -0
- package/admin/pages/users/activity.njk +66 -0
- package/admin/static/admin.css +11 -0
- package/dist/admin/documents.d.ts +101 -0
- package/dist/admin/documents.d.ts.map +1 -1
- package/dist/admin/documents.js +199 -67
- package/dist/admin/documents.js.map +1 -1
- package/dist/admin/federation.d.ts +15 -0
- package/dist/admin/federation.d.ts.map +1 -1
- package/dist/admin/federation.js +19 -0
- package/dist/admin/federation.js.map +1 -1
- package/dist/admin/menu.d.ts.map +1 -1
- package/dist/admin/menu.js +7 -1
- package/dist/admin/menu.js.map +1 -1
- package/dist/admin/photo-field.d.ts +39 -0
- package/dist/admin/photo-field.d.ts.map +1 -0
- package/dist/admin/photo-field.js +91 -0
- package/dist/admin/photo-field.js.map +1 -0
- package/dist/admin/preview.d.ts.map +1 -1
- package/dist/admin/preview.js +6 -1
- package/dist/admin/preview.js.map +1 -1
- package/dist/admin/routes.d.ts.map +1 -1
- package/dist/admin/routes.js +8 -0
- package/dist/admin/routes.js.map +1 -1
- package/dist/admin/syndication-targets.d.ts +57 -0
- package/dist/admin/syndication-targets.d.ts.map +1 -0
- package/dist/admin/syndication-targets.js +266 -0
- package/dist/admin/syndication-targets.js.map +1 -0
- package/dist/admin/templates.d.ts +3 -0
- package/dist/admin/templates.d.ts.map +1 -1
- package/dist/admin/templates.js +3 -0
- package/dist/admin/templates.js.map +1 -1
- package/dist/admin/uploads.d.ts +6 -0
- package/dist/admin/uploads.d.ts.map +1 -1
- package/dist/admin/uploads.js +1 -1
- package/dist/admin/uploads.js.map +1 -1
- package/dist/admin/users.d.ts +8 -0
- package/dist/admin/users.d.ts.map +1 -1
- package/dist/admin/users.js +8 -0
- package/dist/admin/users.js.map +1 -1
- package/dist/content/citation.d.ts +36 -0
- package/dist/content/citation.d.ts.map +1 -0
- package/dist/content/citation.js +31 -0
- package/dist/content/citation.js.map +1 -0
- package/dist/content/index.d.ts +2 -0
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +1 -0
- package/dist/content/index.js.map +1 -1
- package/dist/content/photo.d.ts +45 -0
- package/dist/content/photo.d.ts.map +1 -0
- package/dist/content/photo.js +55 -0
- package/dist/content/photo.js.map +1 -0
- package/dist/content/post-type.d.ts +20 -8
- package/dist/content/post-type.d.ts.map +1 -1
- package/dist/content/post-type.js +17 -0
- package/dist/content/post-type.js.map +1 -1
- package/dist/env.d.ts +7 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/federation/article.d.ts.map +1 -1
- package/dist/federation/article.js +59 -5
- package/dist/federation/article.js.map +1 -1
- package/dist/federation/citations.d.ts +38 -0
- package/dist/federation/citations.d.ts.map +1 -0
- package/dist/federation/citations.js +98 -0
- package/dist/federation/citations.js.map +1 -0
- package/dist/federation/delivery.d.ts.map +1 -1
- package/dist/federation/delivery.js +79 -31
- package/dist/federation/delivery.js.map +1 -1
- package/dist/images/alt-text.d.ts +6 -0
- package/dist/images/alt-text.d.ts.map +1 -1
- package/dist/images/alt-text.js +10 -0
- package/dist/images/alt-text.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -2
- package/dist/index.js.map +1 -1
- package/dist/indieauth/activity-log.d.ts +112 -0
- package/dist/indieauth/activity-log.d.ts.map +1 -0
- package/dist/indieauth/activity-log.js +325 -0
- package/dist/indieauth/activity-log.js.map +1 -0
- package/dist/indieauth/app-activity.d.ts +14 -0
- package/dist/indieauth/app-activity.d.ts.map +1 -0
- package/dist/indieauth/app-activity.js +106 -0
- package/dist/indieauth/app-activity.js.map +1 -0
- package/dist/indieauth/bearer.d.ts +6 -1
- package/dist/indieauth/bearer.d.ts.map +1 -1
- package/dist/indieauth/bearer.js +40 -12
- package/dist/indieauth/bearer.js.map +1 -1
- package/dist/indieauth/consent.d.ts.map +1 -1
- package/dist/indieauth/consent.js +5 -2
- package/dist/indieauth/consent.js.map +1 -1
- package/dist/indieauth/discovery.d.ts +10 -4
- package/dist/indieauth/discovery.d.ts.map +1 -1
- package/dist/indieauth/discovery.js +22 -7
- package/dist/indieauth/discovery.js.map +1 -1
- package/dist/indieauth/token.d.ts.map +1 -1
- package/dist/indieauth/token.js +3 -1
- package/dist/indieauth/token.js.map +1 -1
- package/dist/micropub/create.d.ts +56 -0
- package/dist/micropub/create.d.ts.map +1 -0
- package/dist/micropub/create.js +200 -0
- package/dist/micropub/create.js.map +1 -0
- package/dist/micropub/endpoint.d.ts +21 -0
- package/dist/micropub/endpoint.d.ts.map +1 -0
- package/dist/micropub/endpoint.js +354 -0
- package/dist/micropub/endpoint.js.map +1 -0
- package/dist/micropub/media.d.ts +14 -0
- package/dist/micropub/media.d.ts.map +1 -0
- package/dist/micropub/media.js +94 -0
- package/dist/micropub/media.js.map +1 -0
- package/dist/micropub/update.d.ts +44 -0
- package/dist/micropub/update.d.ts.map +1 -0
- package/dist/micropub/update.js +133 -0
- package/dist/micropub/update.js.map +1 -0
- package/dist/web/context.d.ts +42 -3
- package/dist/web/context.d.ts.map +1 -1
- package/dist/web/context.js +41 -1
- package/dist/web/context.js.map +1 -1
- package/dist/web/render.d.ts +15 -0
- package/dist/web/render.d.ts.map +1 -1
- package/dist/web/render.js +16 -0
- package/dist/web/render.js.map +1 -1
- package/dist/web/routes.js +2 -2
- package/dist/webmention/index.d.ts +2 -0
- package/dist/webmention/index.d.ts.map +1 -1
- package/dist/webmention/index.js +1 -0
- package/dist/webmention/index.js.map +1 -1
- package/dist/webmention/service.d.ts.map +1 -1
- package/dist/webmention/service.js +96 -18
- package/dist/webmention/service.js.map +1 -1
- package/dist/webmention/syndication.d.ts +135 -0
- package/dist/webmention/syndication.d.ts.map +1 -0
- package/dist/webmention/syndication.js +337 -0
- package/dist/webmention/syndication.js.map +1 -0
- package/package.json +1 -1
- package/themes/default/README.md +40 -9
- package/themes/default/layouts/post.njk +19 -6
- package/themes/default/partials/citations.njk +17 -0
- package/themes/default/partials/jsonld.njk +12 -1
- package/themes/default/partials/photos.njk +10 -0
- package/themes/default/partials/post-list.njk +7 -0
- package/themes/default/src/style.css +9 -0
- package/themes/default/static/style.css +16 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A short log of what apps sent to the IndieAuth and Micropub endpoints, and
|
|
3
|
+
* what they were answered (TASK-221), so a site owner can see why a client
|
|
4
|
+
* failed to sign in or to post without reading the client's source.
|
|
5
|
+
*
|
|
6
|
+
* One entry per request to the authorization endpoint (the request as it
|
|
7
|
+
* reaches the consent screen, and the profile redemption), the token
|
|
8
|
+
* endpoint, Micropub and its media endpoint. Each route records through one
|
|
9
|
+
* middleware placed in front of it, which sees the final status and reads the
|
|
10
|
+
* refusal out of the response itself: Micropub's and OAuth's JSON error
|
|
11
|
+
* bodies, or the `error` on an authorization redirect. A handler adds only
|
|
12
|
+
* what its response does not show, through {@link noteActivity}.
|
|
13
|
+
*
|
|
14
|
+
* Nothing secret is kept. No header is read, so no `Authorization` and no
|
|
15
|
+
* cookie; a field named in {@link SECRET_FIELDS} is kept as its name alone,
|
|
16
|
+
* at any depth of a JSON body; a file part is its name, type and size; and
|
|
17
|
+
* every value is cut short. No client address is stored.
|
|
18
|
+
*
|
|
19
|
+
* The file is bounded by count and by age, private to the site's user, and
|
|
20
|
+
* written after the response is decided without the response waiting on it:
|
|
21
|
+
* a write that fails is a warning on the console, never a different answer.
|
|
22
|
+
*/
|
|
23
|
+
import { randomUUID } from 'node:crypto';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
import { findUserById } from '../admin/accounts.js';
|
|
26
|
+
import { readFileIfPresentSync, updateFileAtomically, withFileLock } from '../files/atomic.js';
|
|
27
|
+
/** The log, relative to `dataDir`. */
|
|
28
|
+
export const ACTIVITY_LOG_FILE = 'indieauth-activity.json';
|
|
29
|
+
/** Readable and writable by the site's own user only, like the tokens beside it. */
|
|
30
|
+
export const ACTIVITY_LOG_MODE = 0o600;
|
|
31
|
+
/** How many entries are kept, newest first. */
|
|
32
|
+
export const ACTIVITY_LOG_LIMIT = 100;
|
|
33
|
+
/** How long an entry is kept: fourteen days. */
|
|
34
|
+
export const ACTIVITY_LOG_MAX_AGE_MS = 14 * 24 * 60 * 60 * 1000;
|
|
35
|
+
/** The longest value kept, in characters, before it is cut and marked with an ellipsis. */
|
|
36
|
+
const VALUE_LIMIT = 100;
|
|
37
|
+
/** The most fields one entry lists. */
|
|
38
|
+
const CARRIED_LIMIT = 40;
|
|
39
|
+
/** Fields whose value is never kept, only that they were sent. */
|
|
40
|
+
const SECRET_FIELDS = new Set([
|
|
41
|
+
'access_token',
|
|
42
|
+
'refresh_token',
|
|
43
|
+
'code',
|
|
44
|
+
'code_verifier',
|
|
45
|
+
'client_secret',
|
|
46
|
+
'password',
|
|
47
|
+
'cookie',
|
|
48
|
+
]);
|
|
49
|
+
/** Add to what this request's entry will say. */
|
|
50
|
+
export function noteActivity(c, note) {
|
|
51
|
+
c.set('activityNote', { ...c.var.activityNote, ...note });
|
|
52
|
+
}
|
|
53
|
+
/** Whether an entry is a failure: refused with an error, or answered with an error status. */
|
|
54
|
+
export function isFailure(entry) {
|
|
55
|
+
return entry.error !== undefined || entry.status >= 400;
|
|
56
|
+
}
|
|
57
|
+
/** Whether an entry is still inside the age limit at `now`. */
|
|
58
|
+
export function isFresh(entry, now) {
|
|
59
|
+
return now.getTime() - Date.parse(entry.at) <= ACTIVITY_LOG_MAX_AGE_MS;
|
|
60
|
+
}
|
|
61
|
+
function logFile(dataDir) {
|
|
62
|
+
return path.join(dataDir, ACTIVITY_LOG_FILE);
|
|
63
|
+
}
|
|
64
|
+
function parseLog(text) {
|
|
65
|
+
if (text === undefined)
|
|
66
|
+
return [];
|
|
67
|
+
try {
|
|
68
|
+
const parsed = JSON.parse(text);
|
|
69
|
+
return Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Every entry, newest first. Read in the file's queue, so a read follows the
|
|
77
|
+
* writes queued before it and a screen shows the request that just finished.
|
|
78
|
+
*/
|
|
79
|
+
export async function readActivityLog(dataDir) {
|
|
80
|
+
const file = logFile(dataDir);
|
|
81
|
+
return await withFileLock(file, () => parseLog(readFileIfPresentSync(file)));
|
|
82
|
+
}
|
|
83
|
+
/** Settles once every entry already on its way to the file has landed, so closing never leaves one half written. */
|
|
84
|
+
export async function activityLogSettled(dataDir) {
|
|
85
|
+
await withFileLock(logFile(dataDir), () => undefined);
|
|
86
|
+
}
|
|
87
|
+
async function appendEntry(dataDir, entry, now) {
|
|
88
|
+
await updateFileAtomically(logFile(dataDir), (current) => {
|
|
89
|
+
const kept = parseLog(current).filter((old) => isFresh(old, now));
|
|
90
|
+
const entries = [entry, ...kept].slice(0, ACTIVITY_LOG_LIMIT);
|
|
91
|
+
return `${JSON.stringify({ entries }, null, 2)}\n`;
|
|
92
|
+
}, { mode: ACTIVITY_LOG_MODE });
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The middleware that records one entry per request to a route, after the
|
|
96
|
+
* route has answered. A failure to describe or to write is a warning; the
|
|
97
|
+
* write is not awaited, so the response never waits on the disk.
|
|
98
|
+
*/
|
|
99
|
+
function recordActivity(describe) {
|
|
100
|
+
return async (c, next) => {
|
|
101
|
+
await next();
|
|
102
|
+
const { dataDir, now } = c.var.config;
|
|
103
|
+
try {
|
|
104
|
+
const facts = await describe(c);
|
|
105
|
+
const refusal = c.var.activityNote?.refusal ?? (await refusalIn(c.res));
|
|
106
|
+
const at = now();
|
|
107
|
+
const entry = {
|
|
108
|
+
id: randomUUID(),
|
|
109
|
+
at: at.toISOString(),
|
|
110
|
+
method: c.req.method,
|
|
111
|
+
status: c.res.status,
|
|
112
|
+
...facts,
|
|
113
|
+
error: refusal?.error,
|
|
114
|
+
errorDescription: refusal?.description,
|
|
115
|
+
};
|
|
116
|
+
appendEntry(dataDir, entry, at).catch((error) => {
|
|
117
|
+
warn(error);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
warn(error);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function warn(error) {
|
|
126
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
127
|
+
console.warn(`Could not write the IndieAuth and Micropub activity log: ${reason}`);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The OAuth or Micropub error a response carries: in a JSON error body, or in
|
|
131
|
+
* the query of a redirect back to the client. Only those two fields are read.
|
|
132
|
+
*/
|
|
133
|
+
async function refusalIn(response) {
|
|
134
|
+
const location = response.headers.get('location');
|
|
135
|
+
if (location !== null) {
|
|
136
|
+
const query = URL.parse(location, 'http://localhost/')?.searchParams;
|
|
137
|
+
const error = query?.get('error');
|
|
138
|
+
if (error != null)
|
|
139
|
+
return { error, description: query?.get('error_description') ?? undefined };
|
|
140
|
+
}
|
|
141
|
+
const type = response.headers.get('content-type') ?? '';
|
|
142
|
+
if (response.status < 400 || !/^application\/json\b/i.test(type))
|
|
143
|
+
return undefined;
|
|
144
|
+
const body = (await response.clone().json());
|
|
145
|
+
const error = body['error'];
|
|
146
|
+
const description = body['error_description'];
|
|
147
|
+
if (typeof error !== 'string')
|
|
148
|
+
return undefined;
|
|
149
|
+
return { error, description: typeof description === 'string' ? description : undefined };
|
|
150
|
+
}
|
|
151
|
+
/** `value`, cut to {@link VALUE_LIMIT} characters with an ellipsis when it was longer. */
|
|
152
|
+
function short(value) {
|
|
153
|
+
return value.length > VALUE_LIMIT ? `${value.slice(0, VALUE_LIMIT)}…` : value;
|
|
154
|
+
}
|
|
155
|
+
/** A JSON value as one line of text, every secret field inside it replaced. */
|
|
156
|
+
function summary(value) {
|
|
157
|
+
if (typeof value === 'string')
|
|
158
|
+
return value;
|
|
159
|
+
if (Array.isArray(value) && value.every((item) => typeof item === 'string')) {
|
|
160
|
+
return value.join(', ');
|
|
161
|
+
}
|
|
162
|
+
return (JSON.stringify(value, (key, inner) => SECRET_FIELDS.has(key) ? '[not recorded]' : inner) ?? '');
|
|
163
|
+
}
|
|
164
|
+
function field(name, value) {
|
|
165
|
+
if (SECRET_FIELDS.has(name))
|
|
166
|
+
return { name: short(name), redacted: true };
|
|
167
|
+
return { name: short(name), value: short(summary(value)) };
|
|
168
|
+
}
|
|
169
|
+
function fromParams(params, skip = []) {
|
|
170
|
+
return [...params]
|
|
171
|
+
.filter(([name]) => !skip.includes(name))
|
|
172
|
+
.map(([name, value]) => field(name, value))
|
|
173
|
+
.slice(0, CARRIED_LIMIT);
|
|
174
|
+
}
|
|
175
|
+
function fromForm(form) {
|
|
176
|
+
const fields = [];
|
|
177
|
+
for (const [name, value] of form) {
|
|
178
|
+
fields.push(typeof value === 'string' || SECRET_FIELDS.has(name)
|
|
179
|
+
? field(name, value)
|
|
180
|
+
: {
|
|
181
|
+
name: short(name),
|
|
182
|
+
file: { filename: short(value.name), type: short(value.type), size: value.size },
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return fields.slice(0, CARRIED_LIMIT);
|
|
186
|
+
}
|
|
187
|
+
function isRecord(value) {
|
|
188
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
189
|
+
}
|
|
190
|
+
/** A Micropub JSON body's fields: its `properties` one by one, then everything else. */
|
|
191
|
+
function fromJson(body) {
|
|
192
|
+
if (!isRecord(body))
|
|
193
|
+
return [];
|
|
194
|
+
const fields = [];
|
|
195
|
+
for (const [name, value] of Object.entries(body)) {
|
|
196
|
+
if (name === 'properties' && isRecord(value)) {
|
|
197
|
+
for (const [property, values] of Object.entries(value))
|
|
198
|
+
fields.push(field(property, values));
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
fields.push(field(name, value));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return fields.slice(0, CARRIED_LIMIT);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* The body the route already read, never one it left unread: an upload
|
|
208
|
+
* refused for its size before anything touched it stays untouched.
|
|
209
|
+
*/
|
|
210
|
+
async function readBody(c) {
|
|
211
|
+
if (Object.keys(c.req.bodyCache).length === 0)
|
|
212
|
+
return { kind: 'none' };
|
|
213
|
+
const type = c.req.header('content-type') ?? '';
|
|
214
|
+
if (/^(application\/x-www-form-urlencoded|multipart\/form-data)\b/i.test(type)) {
|
|
215
|
+
return { kind: 'form', form: await c.req.formData() };
|
|
216
|
+
}
|
|
217
|
+
if (/^application\/json\b/i.test(type)) {
|
|
218
|
+
try {
|
|
219
|
+
return { kind: 'json', value: await c.req.json() };
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return { kind: 'none' };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return { kind: 'none' };
|
|
226
|
+
}
|
|
227
|
+
/** What the route's form body said `name` was, when it was text. */
|
|
228
|
+
function formText(form, name) {
|
|
229
|
+
const value = form.get(name);
|
|
230
|
+
return typeof value === 'string' ? short(value) : undefined;
|
|
231
|
+
}
|
|
232
|
+
/** The authorization request, as the consent screen received it. */
|
|
233
|
+
export const logAuthorizationRequest = recordActivity((c) => {
|
|
234
|
+
const params = new URL(c.req.url).searchParams;
|
|
235
|
+
const userId = c.var.session?.userId;
|
|
236
|
+
const user = userId == null ? undefined : findUserById(c.var.config.dataDir, userId);
|
|
237
|
+
const clientId = params.get('client_id');
|
|
238
|
+
return Promise.resolve({
|
|
239
|
+
endpoint: 'authorization',
|
|
240
|
+
action: 'request',
|
|
241
|
+
clientId: clientId === null ? undefined : short(clientId),
|
|
242
|
+
user: user?.username,
|
|
243
|
+
pkce: (params.get('code_challenge') ?? '') !== '' && params.get('code_challenge_method') === 'S256',
|
|
244
|
+
scopes: (params.get('scope') ?? '')
|
|
245
|
+
.split(' ')
|
|
246
|
+
.filter((scope) => scope !== '')
|
|
247
|
+
.map(short),
|
|
248
|
+
carried: fromParams(params),
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
/** A client redeeming its code at the authorization endpoint for the profile. */
|
|
252
|
+
export const logAuthorizationRedemption = recordActivity(async (c) => {
|
|
253
|
+
const body = await readBody(c);
|
|
254
|
+
const form = body.kind === 'form' ? body.form : new FormData();
|
|
255
|
+
return {
|
|
256
|
+
endpoint: 'authorization',
|
|
257
|
+
action: 'redeem',
|
|
258
|
+
clientId: formText(form, 'client_id'),
|
|
259
|
+
user: c.var.activityNote?.user,
|
|
260
|
+
carried: fromForm(form),
|
|
261
|
+
};
|
|
262
|
+
});
|
|
263
|
+
/** A client redeeming a code or a refresh token at the token endpoint. */
|
|
264
|
+
export const logTokenRequest = recordActivity(async (c) => {
|
|
265
|
+
const body = await readBody(c);
|
|
266
|
+
const form = body.kind === 'form' ? body.form : new FormData();
|
|
267
|
+
return {
|
|
268
|
+
endpoint: 'token',
|
|
269
|
+
action: formText(form, 'grant_type') ?? 'authorization_code',
|
|
270
|
+
clientId: formText(form, 'client_id'),
|
|
271
|
+
user: c.var.activityNote?.user,
|
|
272
|
+
scopes: c.var.activityNote?.scopes ?? [],
|
|
273
|
+
carried: fromForm(form),
|
|
274
|
+
};
|
|
275
|
+
});
|
|
276
|
+
/** Who a Micropub request acted as, once the bearer guard let it in. */
|
|
277
|
+
function bearerFacts(c) {
|
|
278
|
+
const { bearer } = c.var;
|
|
279
|
+
return { clientId: bearer?.token.clientId, user: bearer?.user.username };
|
|
280
|
+
}
|
|
281
|
+
/** A query, or a create, update, delete or undelete, at the Micropub endpoint. */
|
|
282
|
+
export const logMicropubRequest = recordActivity(async (c) => {
|
|
283
|
+
if (c.req.method === 'GET') {
|
|
284
|
+
const params = new URL(c.req.url).searchParams;
|
|
285
|
+
return {
|
|
286
|
+
endpoint: 'micropub',
|
|
287
|
+
action: short(`q=${params.get('q') ?? ''}`),
|
|
288
|
+
...bearerFacts(c),
|
|
289
|
+
carried: fromParams(params, ['q']),
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
const body = await readBody(c);
|
|
293
|
+
const carried = body.kind === 'form' ? fromForm(body.form) : body.kind === 'json' ? fromJson(body.value) : [];
|
|
294
|
+
const named = body.kind === 'form'
|
|
295
|
+
? body.form.get('action')
|
|
296
|
+
: body.kind === 'json' && isRecord(body.value)
|
|
297
|
+
? body.value['action']
|
|
298
|
+
: undefined;
|
|
299
|
+
return {
|
|
300
|
+
endpoint: 'micropub',
|
|
301
|
+
action: typeof named === 'string' ? short(named) : 'create',
|
|
302
|
+
...bearerFacts(c),
|
|
303
|
+
carried,
|
|
304
|
+
};
|
|
305
|
+
});
|
|
306
|
+
/** An upload, or `q=last`, at the media endpoint. */
|
|
307
|
+
export const logMediaRequest = recordActivity(async (c) => {
|
|
308
|
+
if (c.req.method === 'GET') {
|
|
309
|
+
const params = new URL(c.req.url).searchParams;
|
|
310
|
+
return {
|
|
311
|
+
endpoint: 'media',
|
|
312
|
+
action: short(`q=${params.get('q') ?? ''}`),
|
|
313
|
+
...bearerFacts(c),
|
|
314
|
+
carried: fromParams(params, ['q']),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
const body = await readBody(c);
|
|
318
|
+
return {
|
|
319
|
+
endpoint: 'media',
|
|
320
|
+
action: 'upload',
|
|
321
|
+
...bearerFacts(c),
|
|
322
|
+
carried: body.kind === 'form' ? fromForm(body.form) : [],
|
|
323
|
+
};
|
|
324
|
+
});
|
|
325
|
+
//# sourceMappingURL=activity-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activity-log.js","sourceRoot":"","sources":["../../src/indieauth/activity-log.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Users > App activity (TASK-221): the recent requests apps made to the
|
|
3
|
+
* IndieAuth and Micropub endpoints, newest first, failures marked, and one
|
|
4
|
+
* request in full. Where a site owner looks when an app will not sign in or
|
|
5
|
+
* post. Any signed-in admin may read it, as with the other admin screens.
|
|
6
|
+
*/
|
|
7
|
+
import type { Hono } from 'hono';
|
|
8
|
+
import type { AdminRender } from '../admin/documents.ts';
|
|
9
|
+
import type { GeekityEnv } from '../env.ts';
|
|
10
|
+
/** Register the list and the screen for one entry. */
|
|
11
|
+
export declare function mountAppActivity(app: Hono<GeekityEnv>, options: {
|
|
12
|
+
render: AdminRender;
|
|
13
|
+
}): void;
|
|
14
|
+
//# sourceMappingURL=app-activity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-activity.d.ts","sourceRoot":"","sources":["../../src/indieauth/app-activity.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAEjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAKzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAe5C,sDAAsD;AACtD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,GAAG,IAAI,CAwC9F"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { formatInTimezone } from '../admin/formatting.js';
|
|
2
|
+
import { readSiteSettings } from '../admin/settings.js';
|
|
3
|
+
import { ADMIN_TEMPLATES } from '../admin/templates.js';
|
|
4
|
+
import { APP_ACTIVITY_CHILD, APP_ACTIVITY_PATH } from '../admin/users.js';
|
|
5
|
+
import { isFailure, isFresh, readActivityLog } from './activity-log.js';
|
|
6
|
+
/** What each endpoint is called on the screen. */
|
|
7
|
+
const ENDPOINT_LABELS = {
|
|
8
|
+
authorization: 'Authorization',
|
|
9
|
+
token: 'Token',
|
|
10
|
+
micropub: 'Micropub',
|
|
11
|
+
media: 'Media',
|
|
12
|
+
};
|
|
13
|
+
/** The query that narrows the list to failures. */
|
|
14
|
+
const FAILURES_QUERY = 'show=failures';
|
|
15
|
+
/** Register the list and the screen for one entry. */
|
|
16
|
+
export function mountAppActivity(app, options) {
|
|
17
|
+
const { render } = options;
|
|
18
|
+
app.get(APP_ACTIVITY_PATH, async (c) => {
|
|
19
|
+
const { config } = c.var;
|
|
20
|
+
const timezone = readSiteSettings(config.contentDir).timezone;
|
|
21
|
+
const entries = await freshEntries(config.dataDir, config.now());
|
|
22
|
+
const failuresOnly = c.req.query('show') === 'failures';
|
|
23
|
+
const failures = entries.filter(isFailure);
|
|
24
|
+
return render(c, ADMIN_TEMPLATES.appActivity, {
|
|
25
|
+
section: 'users',
|
|
26
|
+
child: APP_ACTIVITY_CHILD,
|
|
27
|
+
tabs: [
|
|
28
|
+
{ label: 'All', url: APP_ACTIVITY_PATH, count: entries.length, current: !failuresOnly },
|
|
29
|
+
{
|
|
30
|
+
label: 'Failures',
|
|
31
|
+
url: `${APP_ACTIVITY_PATH}?${FAILURES_QUERY}`,
|
|
32
|
+
count: failures.length,
|
|
33
|
+
current: failuresOnly,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
rows: (failuresOnly ? failures : entries).map((entry) => row(entry, timezone)),
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
app.get(`${APP_ACTIVITY_PATH}/:id`, async (c) => {
|
|
40
|
+
const { config } = c.var;
|
|
41
|
+
const entries = await freshEntries(config.dataDir, config.now());
|
|
42
|
+
const entry = entries.find((candidate) => candidate.id === c.req.param('id'));
|
|
43
|
+
if (entry === undefined)
|
|
44
|
+
return c.notFound();
|
|
45
|
+
const timezone = readSiteSettings(config.contentDir).timezone;
|
|
46
|
+
return render(c, ADMIN_TEMPLATES.appActivityEntry, {
|
|
47
|
+
section: 'users',
|
|
48
|
+
child: APP_ACTIVITY_CHILD,
|
|
49
|
+
listUrl: APP_ACTIVITY_PATH,
|
|
50
|
+
entry: row(entry, timezone),
|
|
51
|
+
details: details(entry),
|
|
52
|
+
carried: entry.carried.map(carriedRow),
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async function freshEntries(dataDir, now) {
|
|
57
|
+
return (await readActivityLog(dataDir)).filter((entry) => isFresh(entry, now));
|
|
58
|
+
}
|
|
59
|
+
/** One entry as the list shows it. */
|
|
60
|
+
function row(entry, timezone) {
|
|
61
|
+
const failed = isFailure(entry);
|
|
62
|
+
return {
|
|
63
|
+
url: `${APP_ACTIVITY_PATH}/${entry.id}`,
|
|
64
|
+
when: { iso: entry.at, text: formatInTimezone(entry.at, timezone) },
|
|
65
|
+
endpoint: ENDPOINT_LABELS[entry.endpoint],
|
|
66
|
+
action: entry.action,
|
|
67
|
+
app: entry.clientId,
|
|
68
|
+
user: entry.user,
|
|
69
|
+
status: entry.status,
|
|
70
|
+
failed,
|
|
71
|
+
error: entry.error,
|
|
72
|
+
errorDescription: entry.errorDescription,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/** What only the full view shows: the method, and what an endpoint adds of its own. */
|
|
76
|
+
function details(entry) {
|
|
77
|
+
const lines = [{ label: 'Method', value: entry.method }];
|
|
78
|
+
if (entry.endpoint === 'authorization' && entry.action === 'request') {
|
|
79
|
+
lines.push({
|
|
80
|
+
label: 'PKCE',
|
|
81
|
+
value: entry.pkce ? 'An S256 code_challenge was sent' : 'No S256 code_challenge was sent',
|
|
82
|
+
}, { label: 'Scopes asked for', value: listed(entry.scopes) });
|
|
83
|
+
}
|
|
84
|
+
if (entry.endpoint === 'token') {
|
|
85
|
+
lines.push({ label: 'Scopes granted', value: listed(entry.scopes) });
|
|
86
|
+
}
|
|
87
|
+
return lines;
|
|
88
|
+
}
|
|
89
|
+
function listed(values) {
|
|
90
|
+
return values.length === 0 ? 'None' : values.join(' ');
|
|
91
|
+
}
|
|
92
|
+
/** One field the request carried, as its row in the full view says it. */
|
|
93
|
+
function carriedRow(field) {
|
|
94
|
+
if ('file' in field) {
|
|
95
|
+
const { filename, type, size } = field.file;
|
|
96
|
+
return {
|
|
97
|
+
name: field.name,
|
|
98
|
+
value: `${filename} (${type === '' ? 'no type' : type}, ${String(size)} bytes)`,
|
|
99
|
+
note: true,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if ('redacted' in field)
|
|
103
|
+
return { name: field.name, value: 'Sent, not recorded', note: true };
|
|
104
|
+
return { name: field.name, value: field.value, note: false };
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=app-activity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-activity.js","sourceRoot":"","sources":["../../src/indieauth/app-activity.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MiddlewareHandler } from 'hono';
|
|
1
|
+
import type { Context, MiddlewareHandler } from 'hono';
|
|
2
2
|
import type { User } from '../admin/accounts.ts';
|
|
3
3
|
import type { GeekityEnv } from '../env.ts';
|
|
4
4
|
import type { Scope } from './request.ts';
|
|
@@ -36,4 +36,9 @@ export interface BearerEnv {
|
|
|
36
36
|
* 9728), which is how an MCP client finds out where to sign in.
|
|
37
37
|
*/
|
|
38
38
|
export declare function requireBearer(guard: Guard): MiddlewareHandler<BearerEnv>;
|
|
39
|
+
/**
|
|
40
|
+
* The 403 for a token that lacks `scope`, for a route that only learns which
|
|
41
|
+
* scope it needs from the request, as a Micropub POST does from its action.
|
|
42
|
+
*/
|
|
43
|
+
export declare function insufficientScope(c: Context<BearerEnv>, scope: Scope): Response;
|
|
39
44
|
//# sourceMappingURL=bearer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bearer.d.ts","sourceRoot":"","sources":["../../src/indieauth/bearer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"bearer.d.ts","sourceRoot":"","sources":["../../src/indieauth/bearer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAGvD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAEzD;;;;;;;GAOG;AACH,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,sBAAsB,CAAC;IACrD,oDAAoD;IACpD,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;CACxB;AAED,+EAA+E;AAC/E,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,UAAU,CAAC,WAAW,CAAC,GAAG;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACzD;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAgCxE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,QAAQ,CAM/E"}
|
package/dist/indieauth/bearer.js
CHANGED
|
@@ -13,7 +13,12 @@ import { findAccessToken, recordUse, verifyAccessToken } from './tokens.js';
|
|
|
13
13
|
export function requireBearer(guard) {
|
|
14
14
|
return async (c, next) => {
|
|
15
15
|
const { config } = c.var;
|
|
16
|
-
const
|
|
16
|
+
const tokens = new Set(await presentedTokens(c));
|
|
17
|
+
if (tokens.size > 1) {
|
|
18
|
+
const description = 'The header and the body carry different access tokens.';
|
|
19
|
+
return refuse(c, 400, 'invalid_request', description, { error: 'invalid_request' });
|
|
20
|
+
}
|
|
21
|
+
const [presented] = tokens;
|
|
17
22
|
if (presented === undefined) {
|
|
18
23
|
return refuse(c, 401, 'unauthorized', 'An access token is required.', {});
|
|
19
24
|
}
|
|
@@ -27,27 +32,50 @@ export function requireBearer(guard) {
|
|
|
27
32
|
return refuse(c, 401, 'invalid_token', description, { error: 'invalid_token' });
|
|
28
33
|
}
|
|
29
34
|
if (guard.scope !== undefined && !token.scopes.includes(guard.scope)) {
|
|
30
|
-
|
|
31
|
-
return refuse(c, 403, 'insufficient_scope', description, {
|
|
32
|
-
error: 'insufficient_scope',
|
|
33
|
-
scope: guard.scope,
|
|
34
|
-
});
|
|
35
|
+
return insufficientScope(c, guard.scope);
|
|
35
36
|
}
|
|
36
37
|
await recordUse(config.dataDir, token, now);
|
|
37
38
|
c.set('bearer', { token, user });
|
|
38
39
|
await next();
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
+
/**
|
|
43
|
+
* The 403 for a token that lacks `scope`, for a route that only learns which
|
|
44
|
+
* scope it needs from the request, as a Micropub POST does from its action.
|
|
45
|
+
*/
|
|
46
|
+
export function insufficientScope(c, scope) {
|
|
47
|
+
const description = `The access token was not granted the ${scope} scope.`;
|
|
48
|
+
return refuse(c, 403, 'insufficient_scope', description, {
|
|
49
|
+
error: 'insufficient_scope',
|
|
50
|
+
scope,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Every access token the request carries. RFC 6750 section 3.1 says a client
|
|
55
|
+
* sends one, but Quill sends the same token in the header and a form body for
|
|
56
|
+
* servers that drop the header, so only two different tokens are refused.
|
|
57
|
+
*/
|
|
58
|
+
async function presentedTokens(c) {
|
|
59
|
+
const tokens = [];
|
|
42
60
|
const header = /^Bearer +(\S+)$/i.exec(c.req.header('authorization') ?? '');
|
|
43
|
-
if (header !==
|
|
44
|
-
|
|
61
|
+
if (header?.[1] !== undefined)
|
|
62
|
+
tokens.push(header[1]);
|
|
45
63
|
const type = c.req.header('content-type') ?? '';
|
|
46
|
-
if (
|
|
64
|
+
if (/^(application\/x-www-form-urlencoded|multipart\/form-data)\b/i.test(type)) {
|
|
65
|
+
const field = (await readBody(c))?.['access_token'];
|
|
66
|
+
if (typeof field === 'string' && field !== '')
|
|
67
|
+
tokens.push(field);
|
|
68
|
+
}
|
|
69
|
+
return tokens;
|
|
70
|
+
}
|
|
71
|
+
/** The parsed form body, or undefined when it cannot be read, which is the route's to refuse. */
|
|
72
|
+
async function readBody(c) {
|
|
73
|
+
try {
|
|
74
|
+
return await c.req.parseBody();
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
47
77
|
return undefined;
|
|
48
78
|
}
|
|
49
|
-
const field = (await c.req.parseBody())['access_token'];
|
|
50
|
-
return typeof field === 'string' && field !== '' ? field : undefined;
|
|
51
79
|
}
|
|
52
80
|
function refuse(c, status, error, description, params) {
|
|
53
81
|
const challenge = Object.entries({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bearer.js","sourceRoot":"","sources":["../../src/indieauth/bearer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGpD,OAAO,EAAE,gCAAgC,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE/E,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AA2B5E;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,KAAY;IACxC,OAAO,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACvB,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC;QACzB,MAAM,
|
|
1
|
+
{"version":3,"file":"bearer.js","sourceRoot":"","sources":["../../src/indieauth/bearer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAGpD,OAAO,EAAE,gCAAgC,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE/E,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AA2B5E;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,KAAY;IACxC,OAAO,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACvB,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC;QACzB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;QACjD,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YACpB,MAAM,WAAW,GAAG,wDAAwD,CAAC;YAC7E,OAAO,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,iBAAiB,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;QACtF,CAAC;QACD,MAAM,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC;QAC3B,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,OAAO,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,cAAc,EAAE,8BAA8B,EAAE,EAAE,CAAC,CAAC;QAC5E,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC;QACzB,MAAM,KAAK,GACT,KAAK,CAAC,QAAQ,KAAK,sBAAsB;YACvC,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC;YACjD,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACxE,MAAM,IAAI,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QAC1F,IAAI,KAAK,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YAC9C,MAAM,WAAW,GAAG,yEAAyE,CAAC;YAC9F,OAAO,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,eAAe,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;QAClF,CAAC;QAED,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YACrE,OAAO,iBAAiB,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QAC3C,CAAC;QAED,MAAM,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QAC5C,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACjC,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,CAAqB,EAAE,KAAY;IACnE,MAAM,WAAW,GAAG,wCAAwC,KAAK,SAAS,CAAC;IAC3E,OAAO,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,oBAAoB,EAAE,WAAW,EAAE;QACvD,KAAK,EAAE,oBAAoB;QAC3B,KAAK;KACN,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,eAAe,CAAC,CAAU;IACvC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5E,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS;QAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IAChD,IAAI,+DAA+D,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/E,MAAM,KAAK,GAAG,CAAC,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,cAAc,CAAC,CAAC;QACpD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE;YAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,iGAAiG;AACjG,KAAK,UAAU,QAAQ,CAAC,CAAU;IAChC,IAAI,CAAC;QACH,OAAO,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,MAAM,CACb,CAAqB,EACrB,MAAuB,EACvB,KAAa,EACb,WAAmB,EACnB,MAAwC;IAExC,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC;QAC/B,GAAG,MAAM;QACT,iBAAiB,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,gCAAgC,EAAE;KAC1E,CAAC;SACC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,KAAK,KAAK,GAAG,CAAC;SAC5C,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,CAAC,CAAC,MAAM,CAAC,kBAAkB,EAAE,UAAU,SAAS,EAAE,CAAC,CAAC;IACpD,CAAC,CAAC,MAAM,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;IACtC,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,EAAE,MAAM,CAAC,CAAC;AACnE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"consent.d.ts","sourceRoot":"","sources":["../../src/indieauth/consent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,MAAM,CAAC;AAI1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAIzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"consent.d.ts","sourceRoot":"","sources":["../../src/indieauth/consent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,MAAM,CAAC;AAI1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAIzD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAa5C,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C;;;GAGG;AACH,eAAO,MAAM,YAAY,6BAAsC,CAAC;AAEhE,mGAAmG;AACnG,eAAO,MAAM,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAOxD,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,CAyBtE;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,IAAI,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,GAAG,IAAI,CA8GhG"}
|
|
@@ -3,6 +3,7 @@ import { ADMIN_PREFIX } from '../admin/session.js';
|
|
|
3
3
|
import { effectiveBaseUrl, readSiteSettings } from '../admin/settings.js';
|
|
4
4
|
import { ADMIN_TEMPLATES } from '../admin/templates.js';
|
|
5
5
|
import { isPrivateHost } from '../webmention/public-address.js';
|
|
6
|
+
import { logAuthorizationRedemption, logAuthorizationRequest, noteActivity, } from './activity-log.js';
|
|
6
7
|
import { fetchClientInformation, fetchClientLogo } from './client.js';
|
|
7
8
|
import { AUTHORIZATION_PATH, authorizationServerMetadata } from './discovery.js';
|
|
8
9
|
import { meForSignIn } from './identity.js';
|
|
@@ -35,7 +36,7 @@ export const SCOPE_LABELS = {
|
|
|
35
36
|
*/
|
|
36
37
|
export function mountAuthorizationEndpoint(app) {
|
|
37
38
|
app.get(AUTHORIZATION_PATH, (c) => c.redirect(`${CONSENT_PATH}${new URL(c.req.url).search}`, 302));
|
|
38
|
-
app.post(AUTHORIZATION_PATH, async (c) => {
|
|
39
|
+
app.post(AUTHORIZATION_PATH, logAuthorizationRedemption, async (c) => {
|
|
39
40
|
c.header('cache-control', 'no-store');
|
|
40
41
|
const { config } = c.var;
|
|
41
42
|
const form = redemptionForm(await c.req.parseBody());
|
|
@@ -48,6 +49,7 @@ export function mountAuthorizationEndpoint(app) {
|
|
|
48
49
|
if (user === undefined) {
|
|
49
50
|
return c.json({ error: 'invalid_grant', error_description: 'The person who approved this is gone.' }, 400);
|
|
50
51
|
}
|
|
52
|
+
noteActivity(c, { user: user.username });
|
|
51
53
|
const baseUrl = effectiveBaseUrl(config, readSiteSettings(config.contentDir));
|
|
52
54
|
return c.json(profileResponse(grant, user, baseUrl));
|
|
53
55
|
});
|
|
@@ -57,9 +59,10 @@ export function mountConsentScreen(app, options) {
|
|
|
57
59
|
const { render } = options;
|
|
58
60
|
function refuse(c, message) {
|
|
59
61
|
c.status(400);
|
|
62
|
+
noteActivity(c, { refusal: { error: 'invalid_request', description: message } });
|
|
60
63
|
return render(c, ADMIN_TEMPLATES.indieauthRefused, { message });
|
|
61
64
|
}
|
|
62
|
-
app.get(CONSENT_PATH, async (c) => {
|
|
65
|
+
app.get(CONSENT_PATH, logAuthorizationRequest, async (c) => {
|
|
63
66
|
const { config } = c.var;
|
|
64
67
|
const user = signedInUser(c);
|
|
65
68
|
if (user === undefined)
|