Skip to content

Commit 25f365a

Browse files
author
ward
committed
Added global default expiry
1 parent 934df90 commit 25f365a

File tree

5 files changed

+154
-50
lines changed

5 files changed

+154
-50
lines changed

paaster/src/lib/i18n/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"empty_paste": "Paste contains no characters",
44
"paste_size_too_large": "Paste size surpasses max size",
55
"themes": "Themes",
6+
"defaultPasteExpiry": "Default paste expiry",
67
"account": {
78
"login": "Login",
89
"logout": "Logout",
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { stringToObjectId } from '$lib/server/objectId';
2+
import { error, json } from '@sveltejs/kit';
3+
import { z } from 'zod';
4+
5+
const defaultSchema = z.object({
6+
expireAfter: z
7+
.string()
8+
.refine((val) => !isNaN(Number(val)), { message: 'Must be a valid number' })
9+
.transform((val) => Number(val))
10+
});
11+
12+
export async function GET({ locals }) {
13+
if (!locals.userId) {
14+
throw error(401);
15+
}
16+
17+
const results = await locals.mongoDb
18+
.collection('userDefaults')
19+
.findOne({ _id: stringToObjectId(locals.userId) });
20+
if (!results) {
21+
throw error(404);
22+
}
23+
24+
return json({
25+
expireAfter: results.expireAfter
26+
});
27+
}
28+
29+
export async function POST({ locals, request }) {
30+
if (!locals.userId) {
31+
throw error(401);
32+
}
33+
34+
const formData = defaultSchema.safeParse(Object.fromEntries(await request.formData()));
35+
36+
if (!formData.success) {
37+
throw error(400, formData.error);
38+
}
39+
40+
await locals.mongoDb
41+
.collection('userDefaults')
42+
.updateOne(
43+
{ _id: stringToObjectId(locals.userId) },
44+
{ $set: { expireAfter: formData.data.expireAfter } },
45+
{ upsert: true }
46+
);
47+
48+
return json({});
49+
}
Lines changed: 58 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,69 @@
11
import { env } from '$env/dynamic/private';
22
import { MAX_UPLOAD_SIZE } from '$lib/consts.js';
3-
import { createPresignedPost } from "@aws-sdk/s3-presigned-post";
3+
import { stringToObjectId } from '$lib/server/objectId.js';
4+
import { createPresignedPost } from '@aws-sdk/s3-presigned-post';
45
import { error, json } from '@sveltejs/kit';
56
import argon2 from 'argon2';
67
import sodium from 'libsodium-wrappers-sumo';
78
import { z } from 'zod';
89

910
const createPasteSchema = z.object({
10-
codeHeader: z.string().trim().max(128),
11-
codeKeySalt: z.string().trim().max(64),
12-
codeName: z.string().trim().max(32).optional(),
13-
codeNameNonce: z.string().trim().max(32).optional(),
14-
codeNameKeySalt: z.string().trim().max(32).optional()
11+
codeHeader: z.string().trim().max(128),
12+
codeKeySalt: z.string().trim().max(64),
13+
codeName: z.string().trim().max(32).optional(),
14+
codeNameNonce: z.string().trim().max(32).optional(),
15+
codeNameKeySalt: z.string().trim().max(32).optional()
1516
});
1617

1718
export async function POST({ locals, request }) {
18-
await sodium.ready;
19-
20-
const formData = createPasteSchema.safeParse(
21-
Object.fromEntries(await request.formData())
22-
);
23-
24-
if (!formData.success) {
25-
throw error(400, formData.error);
26-
}
27-
28-
const accessKey = sodium.to_base64(sodium.randombytes_buf(32));
29-
30-
const createdPaste = await locals.mongoDb.collection('pastes').insertOne({
31-
header: formData.data.codeHeader,
32-
keySalt: formData.data.codeKeySalt,
33-
name: {
34-
value: formData.data.codeName,
35-
nonce: formData.data.codeNameNonce,
36-
keySalt: formData.data.codeNameKeySalt
37-
},
38-
language: null,
39-
expireAfter: -2,
40-
accessKey: await argon2.hash(accessKey),
41-
created: new Date(),
42-
deleteNextRequest: false,
43-
wrapWords: false
44-
});
45-
46-
const signedUrl = await createPresignedPost(locals.s3Client, {
47-
Bucket: env.S3_BUCKET ?? '',
48-
Key: `${createdPaste.insertedId}.bin`,
49-
Conditions: [
50-
['content-length-range', 0, MAX_UPLOAD_SIZE],
51-
],
52-
Expires: 82800,
53-
});
54-
55-
return json({
56-
pasteId: createdPaste.insertedId.toString(),
57-
accessKey: accessKey,
58-
signedUrl: signedUrl
59-
});
60-
}
19+
await sodium.ready;
20+
21+
const formData = createPasteSchema.safeParse(Object.fromEntries(await request.formData()));
22+
23+
if (!formData.success) {
24+
throw error(400, formData.error);
25+
}
26+
27+
let expireAfter = -2;
28+
29+
if (locals.userId) {
30+
const results = await locals.mongoDb
31+
.collection('userDefaults')
32+
.findOne({ _id: stringToObjectId(locals.userId) });
33+
34+
if (results) {
35+
expireAfter = results.expireAfter;
36+
}
37+
}
38+
39+
const accessKey = sodium.to_base64(sodium.randombytes_buf(32));
40+
41+
const createdPaste = await locals.mongoDb.collection('pastes').insertOne({
42+
header: formData.data.codeHeader,
43+
keySalt: formData.data.codeKeySalt,
44+
name: {
45+
value: formData.data.codeName,
46+
nonce: formData.data.codeNameNonce,
47+
keySalt: formData.data.codeNameKeySalt
48+
},
49+
language: null,
50+
expireAfter: expireAfter,
51+
accessKey: await argon2.hash(accessKey),
52+
created: new Date(),
53+
deleteNextRequest: false,
54+
wrapWords: false
55+
});
56+
57+
const signedUrl = await createPresignedPost(locals.s3Client, {
58+
Bucket: env.S3_BUCKET ?? '',
59+
Key: `${createdPaste.insertedId}.bin`,
60+
Conditions: [['content-length-range', 0, MAX_UPLOAD_SIZE]],
61+
Expires: 82800
62+
});
63+
64+
return json({
65+
pasteId: createdPaste.insertedId.toString(),
66+
accessKey: accessKey,
67+
signedUrl: signedUrl
68+
});
69+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { stringToObjectId } from '$lib/server/objectId';
2+
3+
export async function load({ locals }) {
4+
let expireAfter = -2;
5+
if (locals.userId) {
6+
const results = await locals.mongoDb
7+
.collection('userDefaults')
8+
.findOne({ _id: stringToObjectId(locals.userId) });
9+
10+
if (results) {
11+
expireAfter = results.expireAfter;
12+
}
13+
}
14+
15+
return {
16+
expireAfter: expireAfter
17+
};
18+
}

paaster/src/routes/settings/+page.svelte

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
import { onDestroy, onMount } from 'svelte';
1212
import { _ } from '$lib/i18n';
1313
import { get } from 'svelte/store';
14+
import { pasteDeletionTimes } from '$lib/client/paste';
15+
16+
let { data }: { data: { expireAfter: number } } = $props();
1417
1518
let worker: Worker | undefined;
1619
let derivePassword:
@@ -25,7 +28,9 @@
2528
let accountDeleteConfirm: string | undefined = $state();
2629
const accountDeletionConfirmText = get(_)('account.deleteConfirmContent');
2730
28-
onMount(() => {
31+
let defaultPasteDelectionTime = $state(data.expireAfter);
32+
33+
onMount(async () => {
2934
worker = new Worker(new URL('../../workers/derivePassword.ts', import.meta.url), {
3035
type: 'module'
3136
});
@@ -128,6 +133,13 @@
128133
129134
isLoading = false;
130135
}
136+
137+
async function setDefaultPasteExpiry() {
138+
const payload = new FormData();
139+
payload.append('expireAfter', defaultPasteDelectionTime.toString());
140+
141+
await fetch('/api/account/defaults', { method: 'POST', body: payload });
142+
}
131143
</script>
132144

133145
{#if isLoading}
@@ -153,6 +165,21 @@
153165
</div>
154166
</div>
155167

168+
<div class="p-4 pb-0">
169+
<h1 class="text-base-content mb-2 text-3xl">{$_('defaultPasteExpiry')}</h1>
170+
<div class="w-96">
171+
<select
172+
class="select"
173+
onchange={setDefaultPasteExpiry}
174+
bind:value={defaultPasteDelectionTime}
175+
>
176+
{#each pasteDeletionTimes() as period (period.value)}
177+
<option value={period.value}>{period.label}</option>
178+
{/each}
179+
</select>
180+
</div>
181+
</div>
182+
156183
<main class="flex p-5">
157184
<div class="w-full max-w-md">
158185
{#if errorMsg}

0 commit comments

Comments
 (0)