tangled
alpha
login
or
join now
bad-example.com
/
spacedust-utils
6
fork
atom
demos for spacedust
6
fork
atom
overview
issues
pulls
pipelines
logout
ish
bad-example.com
8 months ago
5116dd10
eecda789
+71
-15
3 changed files
expand all
collapse all
unified
split
atproto-notifications
src
App.tsx
server
db.js
index.js
+14
-6
atproto-notifications/src/App.tsx
···
67
67
credentials: 'include',
68
68
});
69
69
if (!res.ok) throw res;
70
70
+
return await res.json();
70
71
}
71
72
72
73
function App() {
···
83
84
const onIdentify = useCallback(async details => {
84
85
setVerif('verifying');
85
86
try {
86
86
-
await verifyUser(host, details.token)
87
87
+
const info = await verifyUser(host, details.token);
87
88
setVerif('verified');
89
89
+
setRole(info.role);
88
90
setUser(details);
89
91
} catch (e) {
90
92
console.error(e);
91
93
setVerif('failed');
92
94
}
93
93
-
// setTimeout(() => {
94
94
-
// setVerif('verified');
95
95
-
// setUser(details);
96
96
-
// }, 400);
97
95
}, [host]);
96
96
+
97
97
+
const logout = useCallback(async () => {
98
98
+
setRole('anonymous');
99
99
+
setUser(null);
100
100
+
// TODO: clear indexeddb
101
101
+
await fetch(`${host}/logout`, {
102
102
+
method: 'POST',
103
103
+
credentials: 'include',
104
104
+
});
105
105
+
});
98
106
99
107
let hasSW = 'serviceWorker' in navigator;
100
108
let hasPush = 'PushManager' in window;
···
163
171
<p>
164
172
<span className="handle">@{user.handle}</span>
165
173
{/* TODO: clear *all* info on logout */}
166
166
-
<button className="subtle bad" onClick={() => setUser(null)}>×</button>
174
174
+
<button className="subtle bad" onClick={logout}>×</button>
167
175
</p>
168
176
</div>
169
177
)}
+6
-1
server/db.js
···
78
78
79
79
this.#stmt_delete_push_sub = db.prepare(
80
80
`delete from push_subs
81
81
-
where session = ?`);
81
81
+
where account_did = ?
82
82
+
and session = ?`);
82
83
83
84
this.#stmt_get_push_info = db.prepare(
84
85
`select created,
···
105
106
}
106
107
this.#stmt_insert_push_sub.run(did, session, sub);
107
108
});
109
109
+
}
110
110
+
111
111
+
removePushSub(did, session) {
112
112
+
return this.#stmt_delete_push_sub.run(did, session);
108
113
}
109
114
110
115
getSubscribedDids() {
+51
-8
server/index.js
···
169
169
'',
170
170
{ ...COOKIE_BASE, expires: new Date(0) },
171
171
));
172
172
-
const getAccountCookie = (req, res, appSecret, adminDid) => {
172
172
+
const getAccountCookie = (req, res, appSecret, adminDid, noDidCheck = false) => {
173
173
const cookies = cookie.parse(req.headers.cookie ?? '');
174
174
const untrusted = cookies['verified-account'] ?? '';
175
175
const json = cookieSig.unsign(untrusted, appSecret);
···
187
187
}
188
188
189
189
// not yet public!!
190
190
-
if (!did || did !== adminDid) {
190
190
+
if (!did || (did !== adminDid && !noDidCheck)) {
191
191
clearAccountCookie(res)
192
192
.setHeader('Content-Type', 'application/json')
193
193
.writeHead(403)
···
241
241
}
242
242
};
243
243
244
244
-
const handleVerify = async (db, req, res, whoamiHost, jwks, appSecret) => {
244
244
+
const handleVerify = async (db, req, res, whoamiHost, jwks, appSecret, adminDid) => {
245
245
const body = await getRequesBody(req);
246
246
const { token } = JSON.parse(body);
247
247
let did;
···
252
252
console.warn('jwks verification failed', e);
253
253
return clearAccountCookie(res).writeHead(400).end(JSON.stringify({ reason: 'verification failed' }));
254
254
}
255
255
+
const isAdmin = did && did === adminDid;
255
256
db.addAccount(did);
256
257
const session = uuidv4();
257
258
setAccountCookie(res, did, session, appSecret);
258
258
-
return res.writeHead(200).end('okayyyy');
259
259
+
return res
260
260
+
.setHeader('Content-Type', 'application/json')
261
261
+
.writeHead(200)
262
262
+
.end(JSON.stringify({ did, role: isAdmin ? 'admin' : 'public' }));
259
263
};
260
264
261
265
const handleSubscribe = async (db, req, res, appSecret, adminDid) => {
···
265
269
const body = await getRequesBody(req);
266
270
const { sub } = JSON.parse(body);
267
271
// addSub('did:plc:z72i7hdynmk6r22z27h6tvur', sub); // DELETEME @bsky.app (DEBUG)
268
268
-
db.addPushSub(did, session, JSON.stringify(sub));
272
272
+
try {
273
273
+
db.addPushSub(did, session, JSON.stringify(sub));
274
274
+
} catch (e) {
275
275
+
console.warn('failed to add sub', e);
276
276
+
return res
277
277
+
.setHeader('Content-Type', 'application/json')
278
278
+
.writeHead(500)
279
279
+
.end(JSON.stringify({ reason: 'failed to register subscription' }));
280
280
+
}
269
281
updateSubs(db);
270
282
res.setHeader('Content-Type', 'application/json');
271
283
res.writeHead(201);
272
284
res.end(JSON.stringify({ sup: 'hi' }));
273
285
};
274
286
287
287
+
const handleLogout = async (db, req, res, appSecret) => {
288
288
+
let info = getAccountCookie(req, res, appSecret, null, true);
289
289
+
if (!info) return res.writeHead(400).end(JSON.stringify({ reason: 'failed to verify cookie signature' }));
290
290
+
const [did, session, _isAdmin] = info;
291
291
+
try {
292
292
+
db.removePushSub(did, session);
293
293
+
} catch (e) {
294
294
+
console.warn('failed to remove sub', e);
295
295
+
return res
296
296
+
.setHeader('Content-Type', 'application/json')
297
297
+
.writeHead(500)
298
298
+
.end(JSON.stringify({ reason: 'failed to register subscription' }));
299
299
+
}
300
300
+
updateSubs(db);
301
301
+
res.setHeader('Content-Type', 'application/json');
302
302
+
res.writeHead(201);
303
303
+
res.end(JSON.stringify({ sup: 'bye' }));
304
304
+
305
305
+
}
306
306
+
275
307
const attempt = listener => async (req, res) => {
276
308
console.log(`-> ${req.method} ${req.url}`);
277
309
try {
···
303
335
}
304
336
if (req.method === 'POST' && req.url === '/verify') {
305
337
res.setHeaders(new Headers(CORS_PERMISSIVE(req)));
306
306
-
return handleVerify(db, req, res, whoamiHost, jwks, secrets.appSecret);
338
338
+
return handleVerify(db, req, res, whoamiHost, jwks, secrets.appSecret, adminDid);
307
339
}
308
340
309
341
if (req.method === 'OPTIONS' && req.url === '/subscribe') {
···
315
347
return handleSubscribe(db, req, res, secrets.appSecret, adminDid);
316
348
}
317
349
318
318
-
res.writeHead(200);
319
319
-
res.end('sup');
350
350
+
if (req.method === 'OPTIONS' && req.url === '/logout') {
351
351
+
// TODO: probably restrict the origin
352
352
+
return res.writeHead(204, CORS_PERMISSIVE(req)).end();
353
353
+
}
354
354
+
if (req.method === 'POST' && req.url === '/logout') {
355
355
+
res.setHeaders(new Headers(CORS_PERMISSIVE(req)));
356
356
+
return handleLogout(db, req, res, secrets.appSecret);
357
357
+
}
358
358
+
359
359
+
res
360
360
+
.setHeaders(new Headers(CORS_PERMISSIVE(req)))
361
361
+
.writeHead(404)
362
362
+
.end('not found (sorry)');
320
363
});
321
364
322
365
const main = env => {