Skip to content

Commit 4e3f04b

Browse files
committed
[testing passed] Add auth check support, separate endpoints by auth requirement, refactor around user id support and use user id over mgmt tokens to update shls
1 parent 4f8e8ab commit 4e3f04b

File tree

4 files changed

+321
-156
lines changed

4 files changed

+321
-156
lines changed

server/db.ts

+192-76
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { base64url, queryString, sqlite } from './deps.ts';
22
import { clientConnectionListener } from './routers/api.ts';
33
import * as types from './types.ts';
44
import { randomStringWithEntropy } from './util.ts';
5+
import env from './config.ts';
56

67
const { DB } = sqlite;
78

@@ -37,20 +38,19 @@ async function updateAccessToken(endpoint: types.HealthLinkEndpoint) {
3738
}
3839

3940
export const DbLinks = {
40-
handleUser(userid: string) {
41-
if (this.userExists(userid)) {
42-
return
43-
}
41+
createUser(userid: string) {
42+
return db.query(`INSERT or ignore INTO user (id) values (?)`, [userid]);
4443
},
4544
create(config: types.HealthLinkConfig) {
45+
this.createUser(config.userId);
4646
const link = {
4747
config,
4848
id: randomStringWithEntropy(32),
4949
managementToken: randomStringWithEntropy(32),
5050
active: true,
5151
};
5252
db.query(
53-
`INSERT INTO shlink (id, management_token, active, config_exp, config_passcode)
53+
`INSERT INTO shlink_access (id, management_token, active, config_exp, config_passcode)
5454
values (:id, :managementToken, :active, :exp, :passcode)`,
5555
{
5656
id: link.id,
@@ -61,30 +61,65 @@ export const DbLinks = {
6161
},
6262
);
6363

64+
db.query(
65+
`INSERT INTO user_shlink (user, shlink)
66+
values (:user, :shlink)`,
67+
{
68+
user: config.userId,
69+
shlink: link.id,
70+
},
71+
);
72+
6473
const link_public = {
6574
id: link.id,
66-
manifestUrl: `${Deno.env.get('PUBliC_URL')}/api/shl/${link.id}`,
75+
manifestUrl: `${env.PUBLIC_URL}/api/shl/${link.id}`,
6776
key: randomStringWithEntropy(32),
6877
flag: link.config.passcode ? 'P' : '',
69-
label: config.label
78+
label: config.label,
79+
version: 1
7080
}
7181

7282
db.query(
73-
`INSERT INTO shlink_public (shlink, manifest_url, encryption_key, flag, label)
74-
values (:shlink, :manifestUrl, :encryptionKey, :flag, :label)`,
83+
`INSERT INTO shlink_public (shlink, manifest_url, encryption_key, flag, label, version)
84+
values (:shlink, :manifestUrl, :encryptionKey, :flag, :label, :version)`,
7585
{
7686
shlink: link_public.id,
7787
manifestUrl: link_public.manifestUrl,
7888
encryptionKey: link_public.key,
7989
flag: link_public.flag,
80-
label: link_public.label
90+
label: link_public.label,
91+
version: link_public.version
8192
},
8293
);
8394

84-
return link_public;
95+
return {
96+
id: link_public.id as string,
97+
url: link_public.manifestUrl as string,
98+
key: link_public.key as string,
99+
flag: link_public.flag as string,
100+
label: link_public.label as string,
101+
v: link_public.version as number,
102+
files: [],
103+
config: {
104+
exp: link.config.exp as number,
105+
passcode: link.config.passcode as string
106+
},
107+
managementToken: link.managementToken as string
108+
};
109+
},
110+
getConfig(shlId: types.HealthLink) {
111+
let shl = db.prepareQuery(`SELECT * from shlink_access where shlink=?`).oneEntry([shlId]);
112+
if (!shl) {
113+
return false;
114+
}
115+
return {
116+
exp: shl.config_exp,
117+
passcode: shl.config_passcode,
118+
label: shl.label
119+
};
85120
},
86121
updateConfig(shl: types.HealthLink) {
87-
let pub = db.query(`SELECT * from shlink_public where shlink=?`).oneEntry([shl.id]);
122+
let pub = db.prepareQuery(`SELECT * from shlink_public where shlink=?`).oneEntry([shl.id]);
88123
if (!pub) {
89124
return false;
90125
}
@@ -104,7 +139,7 @@ export const DbLinks = {
104139
}
105140
);
106141
db.query(
107-
`UPDATE shlink set config_passcode=:passcode, config_exp=:exp where id=:id`,
142+
`UPDATE shlink_access set config_passcode=:passcode, config_exp=:exp where id=:id`,
108143
{
109144
id: shl.id,
110145
exp: shl.config.exp,
@@ -115,50 +150,34 @@ export const DbLinks = {
115150
return true;
116151
},
117152
deactivate(shl: types.HealthLink) {
118-
db.query(`UPDATE shlink set active=false where id=?`, [shl.id]);
119-
return true;
153+
try {
154+
db.query(`UPDATE shlink_access set active=false where id=?`, [shl.id]);
155+
} catch (e) {
156+
return false;
157+
}
158+
return shl.id;
120159
},
121-
reactivate(linkId: string, managementToken: string): boolean {
122-
db.query(`UPDATE shlink set active=true, passcode_failures_remaining=5 where id=? and management_token=?`, [linkId, managementToken]);
160+
reactivate(shl: types.HealthLink): boolean {
161+
db.query(`UPDATE shlink_access set active=true, passcode_failures_remaining=5 where id=?`, [shl.id]);
123162
return true;
124163
},
125164
linkExists(linkId: string): boolean {
126-
return Boolean(db.query(`SELECT * from shlink where id=? and active=1`, [linkId]));
165+
return Boolean(db.query(`SELECT * from shlink_access where id=? and active=1`, [linkId]));
127166
},
128167
userExists(userId: string): boolean {
129168
return Boolean(db.query(`SELECT * from user where id=?`, [userId]));
130169
},
131-
getUserShls(userId: string): types.HealthLink | undefined {
132-
try {
133-
const linkRow = db
134-
.prepareQuery(`SELECT * from shlink where user_id=? and active=1 order by created desc limit 1`)
135-
.allEntries([userId]);
136-
// TODO: fix
137-
const linkPub = db
138-
.prepareQuery(`SELECT * from shlink_public where shlink=?`)
139-
.allEntries([linkRow.id]);
140-
return {
141-
id: linkRow.id as string,
142-
passcodeFailuresRemaining: linkRow.passcode_failures_remaining as number,
143-
active: Boolean(linkRow.active) as boolean,
144-
sessionId: linkRow.session_id as string,
145-
created: linkRow.created as string,
146-
managementToken: linkRow.management_token as string,
147-
config: {
148-
exp: linkRow.config_exp as number,
149-
passcode: linkRow.config_passcode as string,
150-
},
151-
};
152-
} catch (e) {
153-
console.warn(e);
154-
return undefined;
155-
}
170+
managementTokenExists(managementToken: string): boolean {
171+
return Boolean(db.query(`SELECT * from shlink_access where management_token=?`, [managementToken]));
156172
},
157-
getManagedShl(linkId: string, managementToken: string): types.HealthLink {
158-
const linkRow = db
159-
.prepareQuery(`SELECT * from shlink where id=? and management_token=?`)
160-
.oneEntry([linkId, managementToken]);
161-
173+
getManagementTokenUserInternal(managementToken: string): string {
174+
const result = db.prepareQuery(
175+
`SELECT * from shlink_access JOIN user_shlink on shlink_access.id=user_shlink.shlink where management_token=?`
176+
).oneEntry([managementToken]);
177+
return result.user as string;
178+
},
179+
getShlInternal(linkId: string): types.HealthLink {
180+
const linkRow = db.prepareQuery(`SELECT * from shlink_access where id=?`).oneEntry([linkId]);
162181
return {
163182
id: linkRow.id as string,
164183
passcodeFailuresRemaining: linkRow.passcode_failures_remaining as number,
@@ -170,8 +189,11 @@ export const DbLinks = {
170189
},
171190
};
172191
},
173-
getShlInternal(linkId: string): types.HealthLink {
174-
const linkRow = db.prepareQuery(`SELECT * from shlink where id=?`).oneEntry([linkId]);
192+
getManagedShl(linkId: string, managementToken: string): types.HealthLink {
193+
const linkRow = db
194+
.prepareQuery(`SELECT * from shlink_access where id=? and management_token=?`)
195+
.oneEntry([linkId, managementToken]);
196+
175197
return {
176198
id: linkRow.id as string,
177199
passcodeFailuresRemaining: linkRow.passcode_failures_remaining as number,
@@ -183,44 +205,123 @@ export const DbLinks = {
183205
},
184206
};
185207
},
186-
async addFile(linkId: string, file: types.HealthLinkFile): Promise<string> {
208+
getUserShl(linkId: string, userId: string): types.HealthLink {
209+
try {
210+
const userRow = db
211+
.prepareQuery(`SELECT * from user_shlink where shlink=? and user=?`)
212+
.oneEntry([linkId, userId]);
213+
214+
if (!userRow) {
215+
return;
216+
}
217+
218+
const linkRow = db
219+
.prepareQuery(`SELECT * from shlink_access where id=?`)
220+
.oneEntry([linkId]);
221+
222+
return {
223+
id: linkRow.id as string,
224+
passcodeFailuresRemaining: linkRow.passcode_failures_remaining as number,
225+
active: Boolean(linkRow.active) as boolean,
226+
managementToken: linkRow.management_token as string,
227+
config: {
228+
exp: linkRow.config_exp as number,
229+
passcode: linkRow.config_passcode as string,
230+
},
231+
};
232+
} catch (e) {
233+
console.error(e);
234+
return;
235+
}
236+
},
237+
getUserShls(userId: string): Array<types.HealthLink> | undefined {
238+
try {
239+
const userPubShls = db
240+
.prepareQuery(`
241+
SELECT
242+
shlink_public.*,
243+
shlink_access.config_passcode,
244+
shlink_access.config_exp,
245+
shlink_access.management_token
246+
FROM user_shlink
247+
JOIN shlink_public on shlink_public.shlink=user_shlink.shlink
248+
JOIN shlink_access on shlink_access.id=user_shlink.shlink
249+
WHERE
250+
user_shlink.user=?
251+
and shlink_access.active=1
252+
`)
253+
.allEntries([userId])
254+
.map( row => {
255+
return {
256+
id: row.shlink as string,
257+
url: row.manifest_url as string,
258+
exp: row.config_exp as number,
259+
key: row.encryption_key as string,
260+
flag: row.flag as string,
261+
label: row.label as string,
262+
v: row.version as number,
263+
files: this.getFiles(row.shlink as string),
264+
config: {
265+
exp: row.config_exp as number,
266+
passcode: row.config_passcode as string
267+
},
268+
managementToken: row.management_token as string
269+
}
270+
});
271+
return userPubShls;
272+
} catch (e) {
273+
console.error(e);
274+
}
275+
},
276+
async addFile(linkId: string, file: types.HealthLinkFile) {
187277
const hash = await crypto.subtle.digest('SHA-256', file.content);
188278
const hashEncoded = base64url.encode(hash);
189-
db.query(`insert or ignore into cas_item(hash, content) values(:hashEncoded, :content)`, {
190-
hashEncoded,
191-
content: file.content,
192-
});
193-
194-
db.query(
195-
`insert into shlink_file(shlink, content_type, content_hash) values (:linkId, :contentType, :hashEncoded)`,
196-
{
197-
linkId,
198-
contentType: file.contentType,
199-
hashEncoded,
200-
},
201-
);
279+
try {
280+
db.transaction(() => {
281+
db.query(`insert or ignore into cas_item(hash, content) values(:hashEncoded, :content)`, {
282+
hashEncoded,
283+
content: file.content,
284+
});
285+
286+
db.query(
287+
`insert into shlink_file(shlink, content_type, content_hash) values (:linkId, :contentType, :hashEncoded)`,
288+
{
289+
linkId,
290+
contentType: file.contentType,
291+
hashEncoded,
292+
},
293+
);
294+
});
295+
} catch (e) {
296+
console.error(e);
297+
return false;
298+
}
202299

203300
return hashEncoded;
204301
},
205302
async deleteFile(linkId: string, content: Uint8Array) {
206303
const hash = await crypto.subtle.digest('SHA-256', content);
207304
const hashEncoded = base64url.encode(hash);
208-
209-
db.query(
210-
`delete from shlink_file where shlink = :linkId and content_hash = :hashEncoded`,
211-
{
212-
linkId,
213-
hashEncoded,
214-
}
215-
);
216-
305+
try {
306+
db.query(
307+
`delete from shlink_file where shlink = :linkId and content_hash = :hashEncoded`,
308+
{
309+
linkId,
310+
hashEncoded,
311+
}
312+
);
313+
} catch (e) {
314+
console.error(e);
315+
return false;
316+
}
317+
// Hard delete
217318
// db.query(`delete from cas_item where hash = :hashEncoded and content = :content`,
218319
// {
219320
// hashEncoded,
220321
// content: file.content,
221322
// });
222323

223-
return true;
324+
return hashEncoded as string;
224325
},
225326
async deleteAllFiles(linkId: string) {
226327

@@ -352,8 +453,23 @@ export const DbLinks = {
352453
contentType: fileRow[0].content_type,
353454
};
354455
},
456+
getFiles(shlId: string): types.HealthLinkFile[] {
457+
const files = db.queryEntries<{
458+
label: string;
459+
added_time: string;
460+
content_type: string;
461+
}>(
462+
`select * from shlink_file where shlink=?`,
463+
[shlId],
464+
);
465+
return files.map((f) => ({
466+
label: f.label,
467+
added: f.added_time,
468+
contentType: f.content_type,
469+
}));
470+
},
355471
recordAccess(shlId: string, recipient: string) {
356-
const q = db.prepareQuery(`insert into shlink_access(shlink, recipient) values (?, ?)`);
472+
const q = db.prepareQuery(`insert into shlink_access_log(shlink, recipient) values (?, ?)`);
357473
q.execute([shlId, recipient]);
358474

359475
clientConnectionListener({
@@ -363,7 +479,7 @@ export const DbLinks = {
363479
},
364480
recordPasscodeFailure(shlId: string) {
365481
const q = db.prepareQuery(
366-
`update shlink set passcode_failures_remaining = passcode_failures_remaining - 1 where id=?`
482+
`update shlink_access set passcode_failures_remaining = passcode_failures_remaining - 1 where id=?`
367483
);
368484
q.execute([shlId]);
369485
},

0 commit comments

Comments
 (0)