refactor: require password verification
chore: styling work
This commit is contained in:
parent
7977fe9ea7
commit
86fa2b6d4b
16 changed files with 348 additions and 88 deletions
|
|
@ -46,10 +46,10 @@
|
||||||
"@andyburke/lurid": "jsr:@andyburke/lurid@^0.2.0",
|
"@andyburke/lurid": "jsr:@andyburke/lurid@^0.2.0",
|
||||||
"@andyburke/serverus": "jsr:@andyburke/serverus@^0.16.0",
|
"@andyburke/serverus": "jsr:@andyburke/serverus@^0.16.0",
|
||||||
"@da/bcrypt": "jsr:@da/bcrypt@^1.0.1",
|
"@da/bcrypt": "jsr:@da/bcrypt@^1.0.1",
|
||||||
"@std/assert": "jsr:@std/assert@^1.0.17",
|
"@std/assert": "jsr:@std/assert@^1.0.19",
|
||||||
"@std/encoding": "jsr:@std/encoding@^1.0.10",
|
"@std/encoding": "jsr:@std/encoding@^1.0.10",
|
||||||
"@std/fs": "jsr:@std/fs@^1.0.22",
|
"@std/fs": "jsr:@std/fs@^1.0.23",
|
||||||
"@std/http": "jsr:@std/http@^1.0.23",
|
"@std/http": "jsr:@std/http@^1.0.25",
|
||||||
"@std/media-types": "jsr:@std/media-types@^1.1.0",
|
"@std/media-types": "jsr:@std/media-types@^1.1.0",
|
||||||
"@std/path": "jsr:@std/path@^1.1.4"
|
"@std/path": "jsr:@std/path@^1.1.4"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
63
deno.lock
generated
63
deno.lock
generated
|
|
@ -6,28 +6,27 @@
|
||||||
"jsr:@andyburke/serverus@0.16": "0.16.0",
|
"jsr:@andyburke/serverus@0.16": "0.16.0",
|
||||||
"jsr:@da/bcrypt@*": "1.0.1",
|
"jsr:@da/bcrypt@*": "1.0.1",
|
||||||
"jsr:@da/bcrypt@^1.0.1": "1.0.1",
|
"jsr:@da/bcrypt@^1.0.1": "1.0.1",
|
||||||
"jsr:@std/assert@^1.0.17": "1.0.17",
|
"jsr:@std/assert@^1.0.19": "1.0.19",
|
||||||
"jsr:@std/cli@^1.0.19": "1.0.25",
|
"jsr:@std/cli@^1.0.19": "1.0.28",
|
||||||
"jsr:@std/cli@^1.0.20": "1.0.25",
|
"jsr:@std/cli@^1.0.20": "1.0.28",
|
||||||
"jsr:@std/cli@^1.0.21": "1.0.25",
|
"jsr:@std/cli@^1.0.21": "1.0.28",
|
||||||
"jsr:@std/cli@^1.0.25": "1.0.25",
|
"jsr:@std/cli@^1.0.28": "1.0.28",
|
||||||
"jsr:@std/encoding@^1.0.10": "1.0.10",
|
"jsr:@std/encoding@^1.0.10": "1.0.10",
|
||||||
"jsr:@std/fmt@^1.0.6": "1.0.8",
|
"jsr:@std/fmt@^1.0.6": "1.0.9",
|
||||||
"jsr:@std/fmt@^1.0.8": "1.0.8",
|
"jsr:@std/fmt@^1.0.9": "1.0.9",
|
||||||
"jsr:@std/fs@^1.0.18": "1.0.22",
|
"jsr:@std/fs@^1.0.18": "1.0.23",
|
||||||
"jsr:@std/fs@^1.0.19": "1.0.22",
|
"jsr:@std/fs@^1.0.19": "1.0.23",
|
||||||
"jsr:@std/fs@^1.0.21": "1.0.22",
|
"jsr:@std/fs@^1.0.23": "1.0.23",
|
||||||
"jsr:@std/fs@^1.0.22": "1.0.22",
|
|
||||||
"jsr:@std/html@^1.0.5": "1.0.5",
|
"jsr:@std/html@^1.0.5": "1.0.5",
|
||||||
"jsr:@std/http@^1.0.20": "1.0.23",
|
"jsr:@std/http@^1.0.20": "1.0.25",
|
||||||
"jsr:@std/http@^1.0.23": "1.0.23",
|
"jsr:@std/http@^1.0.25": "1.0.25",
|
||||||
"jsr:@std/internal@^1.0.12": "1.0.12",
|
"jsr:@std/internal@^1.0.12": "1.0.12",
|
||||||
"jsr:@std/media-types@^1.1.0": "1.1.0",
|
"jsr:@std/media-types@^1.1.0": "1.1.0",
|
||||||
"jsr:@std/net@^1.0.6": "1.0.6",
|
"jsr:@std/net@^1.0.6": "1.0.6",
|
||||||
"jsr:@std/path@^1.1.0": "1.1.4",
|
"jsr:@std/path@^1.1.0": "1.1.4",
|
||||||
"jsr:@std/path@^1.1.1": "1.1.4",
|
"jsr:@std/path@^1.1.1": "1.1.4",
|
||||||
"jsr:@std/path@^1.1.4": "1.1.4",
|
"jsr:@std/path@^1.1.4": "1.1.4",
|
||||||
"jsr:@std/streams@^1.0.16": "1.0.16",
|
"jsr:@std/streams@^1.0.17": "1.0.17",
|
||||||
"npm:@types/node@*": "22.15.15"
|
"npm:@types/node@*": "22.15.15"
|
||||||
},
|
},
|
||||||
"jsr": {
|
"jsr": {
|
||||||
|
|
@ -59,23 +58,23 @@
|
||||||
"@da/bcrypt@1.0.1": {
|
"@da/bcrypt@1.0.1": {
|
||||||
"integrity": "d2172d3acbcff52e0465557a1a48b1ff1c92df08c90712dae5372255a8c45eb3"
|
"integrity": "d2172d3acbcff52e0465557a1a48b1ff1c92df08c90712dae5372255a8c45eb3"
|
||||||
},
|
},
|
||||||
"@std/assert@1.0.17": {
|
"@std/assert@1.0.19": {
|
||||||
"integrity": "df5ebfffe77c03b3fa1401e11c762cc8f603d51021c56c4d15a8c7ab45e90dbe",
|
"integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"jsr:@std/internal"
|
"jsr:@std/internal"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@std/cli@1.0.25": {
|
"@std/cli@1.0.28": {
|
||||||
"integrity": "1f85051b370c97a7a9dfc6ba626e7ed57a91bea8c081597276d1e78d929d8c91"
|
"integrity": "74ef9b976db59ca6b23a5283469c9072be6276853807a83ec6c7ce412135c70a"
|
||||||
},
|
},
|
||||||
"@std/encoding@1.0.10": {
|
"@std/encoding@1.0.10": {
|
||||||
"integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1"
|
"integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1"
|
||||||
},
|
},
|
||||||
"@std/fmt@1.0.8": {
|
"@std/fmt@1.0.9": {
|
||||||
"integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7"
|
"integrity": "2487343e8899fb2be5d0e3d35013e54477ada198854e52dd05ed0422eddcabe0"
|
||||||
},
|
},
|
||||||
"@std/fs@1.0.22": {
|
"@std/fs@1.0.23": {
|
||||||
"integrity": "de0f277a58a867147a8a01bc1b181d0dfa80bfddba8c9cf2bacd6747bcec9308",
|
"integrity": "3ecbae4ce4fee03b180fa710caff36bb5adb66631c46a6460aaad49515565a37",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"jsr:@std/internal",
|
"jsr:@std/internal",
|
||||||
"jsr:@std/path@^1.1.4"
|
"jsr:@std/path@^1.1.4"
|
||||||
|
|
@ -84,13 +83,13 @@
|
||||||
"@std/html@1.0.5": {
|
"@std/html@1.0.5": {
|
||||||
"integrity": "4e2d693f474cae8c16a920fa5e15a3b72267b94b84667f11a50c6dd1cb18d35e"
|
"integrity": "4e2d693f474cae8c16a920fa5e15a3b72267b94b84667f11a50c6dd1cb18d35e"
|
||||||
},
|
},
|
||||||
"@std/http@1.0.23": {
|
"@std/http@1.0.25": {
|
||||||
"integrity": "6634e9e034c589bf35101c1b5ee5bbf052a5987abca20f903e58bdba85c80dee",
|
"integrity": "577b4252290af1097132812b339fffdd55fb0f4aeb98ff11bdbf67998aa17193",
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"jsr:@std/cli@^1.0.25",
|
"jsr:@std/cli@^1.0.28",
|
||||||
"jsr:@std/encoding",
|
"jsr:@std/encoding",
|
||||||
"jsr:@std/fmt@^1.0.8",
|
"jsr:@std/fmt@^1.0.9",
|
||||||
"jsr:@std/fs@^1.0.21",
|
"jsr:@std/fs@^1.0.23",
|
||||||
"jsr:@std/html",
|
"jsr:@std/html",
|
||||||
"jsr:@std/media-types",
|
"jsr:@std/media-types",
|
||||||
"jsr:@std/net",
|
"jsr:@std/net",
|
||||||
|
|
@ -113,8 +112,8 @@
|
||||||
"jsr:@std/internal"
|
"jsr:@std/internal"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"@std/streams@1.0.16": {
|
"@std/streams@1.0.17": {
|
||||||
"integrity": "85030627befb1767c60d4f65cb30fa2f94af1d6ee6e5b2515b76157a542e89c4"
|
"integrity": "7859f3d9deed83cf4b41f19223d4a67661b3d3819e9fc117698f493bf5992140"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npm": {
|
"npm": {
|
||||||
|
|
@ -137,10 +136,10 @@
|
||||||
"jsr:@andyburke/lurid@0.2",
|
"jsr:@andyburke/lurid@0.2",
|
||||||
"jsr:@andyburke/serverus@0.16",
|
"jsr:@andyburke/serverus@0.16",
|
||||||
"jsr:@da/bcrypt@^1.0.1",
|
"jsr:@da/bcrypt@^1.0.1",
|
||||||
"jsr:@std/assert@^1.0.17",
|
"jsr:@std/assert@^1.0.19",
|
||||||
"jsr:@std/encoding@^1.0.10",
|
"jsr:@std/encoding@^1.0.10",
|
||||||
"jsr:@std/fs@^1.0.22",
|
"jsr:@std/fs@^1.0.23",
|
||||||
"jsr:@std/http@^1.0.23",
|
"jsr:@std/http@^1.0.25",
|
||||||
"jsr:@std/media-types@^1.1.0",
|
"jsr:@std/media-types@^1.1.0",
|
||||||
"jsr:@std/path@^1.1.4"
|
"jsr:@std/path@^1.1.4"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -41,13 +41,6 @@ export async function GET(request: Request, meta: Record<string, any>): Promise<
|
||||||
event_id
|
event_id
|
||||||
} = /^.*\/events\/.*\/(?<event_type>.*?)\:(?<event_id>[A-Za-z-]+)\.json$/.exec(entry.path)?.groups ?? {};
|
} = /^.*\/events\/.*\/(?<event_type>.*?)\:(?<event_id>[A-Za-z-]+)\.json$/.exec(entry.path)?.groups ?? {};
|
||||||
|
|
||||||
console.dir({
|
|
||||||
entry,
|
|
||||||
event_type,
|
|
||||||
event_id,
|
|
||||||
query: meta.query
|
|
||||||
});
|
|
||||||
|
|
||||||
if (meta.query.after_id && event_id <= meta.query.after_id) {
|
if (meta.query.after_id && event_id <= meta.query.after_id) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,22 @@ export async function POST(req: Request, meta: Record<string, any>): Promise<Res
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const password_verification_hash: string = body.password_verification_hash ?? (typeof body.password_verification === 'string'
|
||||||
|
? encodeBase64(
|
||||||
|
await crypto.subtle.digest('SHA-256', new TextEncoder().encode(body.password_verification))
|
||||||
|
)
|
||||||
|
: '');
|
||||||
|
if (password_verification_hash !== password_hash) {
|
||||||
|
return Response.json({
|
||||||
|
error: {
|
||||||
|
cause: 'invalid password verification hash',
|
||||||
|
message: 'Password and verification must be identical.'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
status: 400
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const at_least_one_existing_user = (await USERS.all({
|
const at_least_one_existing_user = (await USERS.all({
|
||||||
limit: 1,
|
limit: 1,
|
||||||
offset: 0
|
offset: 0
|
||||||
|
|
|
||||||
|
|
@ -2,45 +2,61 @@
|
||||||
:root {
|
:root {
|
||||||
--base-color: #234;
|
--base-color: #234;
|
||||||
|
|
||||||
--bg: hsl(from var(--base-color) h 20% 7.5%);
|
--bg: hsl(from var(--base-color) h 20% 10%);
|
||||||
--bg-darker: hsl(from var(--base-color) h 20% 5%);
|
--bg-darker: hsl(from var(--base-color) h 20% 7.5%);
|
||||||
--bg-lighter: hsl(from var(--base-color) h 20% 10%);
|
--bg-lighter: hsl(from var(--base-color) h 20% 12.5%);
|
||||||
|
--bg-card: hsl(from var(--base-color) h 20% 15%);
|
||||||
|
--bg-card-hover: hsl(from var(--base-color) h 20% 16%);
|
||||||
|
|
||||||
|
--text-primary: hsl(from var(--base-color) h 5% 95%);
|
||||||
|
--text-secondary: hsl(from var(--base-color) h 5% 85%);
|
||||||
|
--text-muted: hsl(from var(--base-color) h 5% 75%);
|
||||||
|
|
||||||
|
--accent: hsl(from var(--base-color) h clamp(0, calc(s + 10), 100) clamp(0, calc(l + 20), 100));
|
||||||
|
--accent-dark: hsl(from var(--base-color) h clamp(0, calc(s + 5), 100) clamp(0, calc(l - 5), 100));
|
||||||
|
|
||||||
|
--border-subtle: hsl(from var(--base-color) h 50% 90%);
|
||||||
|
--border-normal: hsl(from var(--base-color) h 50% 80%);
|
||||||
|
--border-highlight: hsl(from var(--base-color) h 50% 25%);
|
||||||
|
|
||||||
|
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
|
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
|
||||||
|
--shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
--blur-radius: 8px;
|
--blur-radius: 8px;
|
||||||
|
|
||||||
--text: hsl(from var(--base-color) h 5% 100%);
|
|
||||||
--accent: hsl(from var(--base-color) h clamp(0, calc(s + 10), 100) clamp(0, calc(l + 20), 100));
|
|
||||||
|
|
||||||
--border-subtle: hsl(from var(--base-color) h 50% 25%);
|
|
||||||
--border-normal: hsl(from var(--base-color) h 50% 50%);
|
|
||||||
--border-highlight: hsl(from var(--base-color) h 50% 75%);
|
|
||||||
|
|
||||||
--icon-scale: 1;
|
--icon-scale: 1;
|
||||||
--border-radius: 4px;
|
--border-radius: 8px;
|
||||||
|
--border-radius-lg: 12px;
|
||||||
|
|
||||||
|
--space-xxs: 0.25rem;
|
||||||
|
--space-xs: 0.5rem;
|
||||||
|
--space-sm: 0.75rem;
|
||||||
|
--space-md: 1rem;
|
||||||
|
--space-lg: 1.5rem;
|
||||||
|
--space-xl: 2rem;
|
||||||
|
--space-xxl: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
:root {
|
:root {
|
||||||
--bg: hsl(from var(--base-color) h 20% 93%);
|
--bg: hsl(from var(--base-color) h 20% 98%);
|
||||||
--bg-darker: hsl(from var(--base-color) h 20% 88%);
|
--bg-darker: hsl(from var(--base-color) h 20% 96%);
|
||||||
--bg-lighter: hsl(from var(--base-color) h 20% 98%);
|
--bg-lighter: hsl(from var(--base-color) h 20% 99%);
|
||||||
|
--bg-card: hsl(from var(--base-color) h 20% 100%);
|
||||||
|
--bg-card-hover: hsl(from var(--base-color) h 20% 98%);
|
||||||
|
|
||||||
--text: hsl(from var(--base-color) h 5% 5%);
|
--text-primary: hsl(from var(--base-color) h 5% 5%);
|
||||||
--accent: hsl(from var(--base-color) h calc(s + 10) calc(l + 20));
|
--text-secondary: hsl(from var(--base-color) h 5% 30%);
|
||||||
|
--text-muted: hsl(from var(--base-color) h 5% 50%);
|
||||||
|
|
||||||
--border-subtle: hsl(from var(--base-color) h 50% 90%);
|
--border-subtle: hsl(from var(--base-color) h 50% 85%);
|
||||||
--border-normal: hsl(from var(--base-color) h 50% 50%);
|
--border-normal: hsl(from var(--base-color) h 50% 70%);
|
||||||
--border-highlight: hsl(from var var(--base-color) h 50% 25%);
|
--border-highlight: hsl(from var(--base-color) h 50% 35%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Box sizing rules */
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Remove default margin in favour of better control in authored CSS */
|
/* Remove default margin in favour of better control in authored CSS */
|
||||||
body,
|
body,
|
||||||
h1,
|
h1,
|
||||||
|
|
@ -157,7 +173,7 @@ textarea:not([rows]) {
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
color: var(--text);
|
color: var(--text-primary);
|
||||||
background-color: var(--bg);
|
background-color: var(--bg);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -201,7 +217,7 @@ textarea:focus {
|
||||||
|
|
||||||
.avatar-container {
|
.avatar-container {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0.5rem auto;
|
margin: 0 auto 1rem;
|
||||||
width: 3rem;
|
width: 3rem;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
|
|
@ -281,7 +297,7 @@ label:has(input[collapse-toggle]) {
|
||||||
display: block;
|
display: block;
|
||||||
max-width: fit-content;
|
max-width: fit-content;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1px solid var(--text);
|
border: 1px solid var(--border-subtle);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
|
|
@ -334,14 +350,15 @@ form input:focus {
|
||||||
form button {
|
form button {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border: 1px solid var(--text);
|
border: 1px solid var(--border-subtle);
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
background-color: var(--bg);
|
background-color: var(--bg);
|
||||||
color: var(--text);
|
color: var(--text-primary);
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
form button.primary {
|
form button.primary {
|
||||||
|
border-color: var(--border-highlight);
|
||||||
background-color: var(--accent);
|
background-color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -349,7 +366,7 @@ button {
|
||||||
background: inherit;
|
background: inherit;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
border: 1px solid var(--text);
|
border: 1px solid var(--text-primary);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -582,6 +582,49 @@
|
||||||
left: -6px;
|
left: -6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ICON - MIC */
|
||||||
|
.icon.mic {
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
transform: scale(var(--icon-scale, 1));
|
||||||
|
width: 16px;
|
||||||
|
height: 12px;
|
||||||
|
border-bottom-left-radius: 120px;
|
||||||
|
border-bottom-right-radius: 120px;
|
||||||
|
border: 2px solid;
|
||||||
|
border-top: 0;
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon.mic::after,
|
||||||
|
.icon.mic::before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon.mic::after {
|
||||||
|
border: 2px solid;
|
||||||
|
width: 8px;
|
||||||
|
height: 18px;
|
||||||
|
left: 2px;
|
||||||
|
top: -10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon.mic::before {
|
||||||
|
width: 10px;
|
||||||
|
height: 4px;
|
||||||
|
top: 12px;
|
||||||
|
left: 1px;
|
||||||
|
border-right: 4px solid transparent;
|
||||||
|
box-shadow:
|
||||||
|
0 2px 0,
|
||||||
|
inset -2px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* ICON - MINUS */
|
/* ICON - MINUS */
|
||||||
.icon.minus {
|
.icon.minus {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ function embed_vimeo(link_info) {
|
||||||
<iframe
|
<iframe
|
||||||
src="https://player.vimeo.com/video/${video_id}"
|
src="https://player.vimeo.com/video/${video_id}"
|
||||||
frameborder="0"
|
frameborder="0"
|
||||||
allow="fullscreen; picture-in-picture; clipboard-write; encrypted-media; web-share"
|
allow="fullscreen; picture-in-picture; clipboard-write; encrypted-media;"
|
||||||
referrerpolicy="strict-origin-when-cross-origin"
|
referrerpolicy="strict-origin-when-cross-origin"
|
||||||
title="Star Trek: Legacy"
|
title="Star Trek: Legacy"
|
||||||
loading="lazy"></iframe>
|
loading="lazy"></iframe>
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ function embed_youtube(link_info) {
|
||||||
<iframe
|
<iframe
|
||||||
src="https://www.youtube.com/embed/${video_id}"
|
src="https://www.youtube.com/embed/${video_id}"
|
||||||
title="YouTube video player"
|
title="YouTube video player"
|
||||||
allow="clipboard-write; encrypted-media; picture-in-picture; web-share;"
|
allow="clipboard-write; encrypted-media; picture-in-picture;"
|
||||||
referrerpolicy="strict-origin-when-cross-origin"
|
referrerpolicy="strict-origin-when-cross-origin"
|
||||||
allowfullscreen
|
allowfullscreen
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--text);
|
color: var(--text-primary);
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
transition:
|
transition:
|
||||||
background 0.25s,
|
background 0.25s,
|
||||||
|
|
@ -257,7 +257,7 @@
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
margin: 0 0.75rem 1rem 0;
|
margin: 0 0.75rem 1rem 0;
|
||||||
background: none;
|
background: none;
|
||||||
color: var(--text);
|
color: var(--text-primary);
|
||||||
border: 1px solid var(--border-highlight);
|
border: 1px solid var(--border-highlight);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|
@ -364,7 +364,7 @@
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
padding: 0.75rem;
|
padding: 1rem;
|
||||||
width: 6rem;
|
width: 6rem;
|
||||||
border-right: 1px solid var(--border-subtle);
|
border-right: 1px solid var(--border-subtle);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
@ -421,7 +421,7 @@
|
||||||
#server-info {
|
#server-info {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0.75rem;
|
padding: 1rem 2rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div id="server-info">
|
<div id="server-info">
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,10 @@
|
||||||
<input id="signup-password" type="password" name="password" required />
|
<input id="signup-password" type="password" name="password" required />
|
||||||
<label class="placeholder" for="signup-password">password</label>
|
<label class="placeholder" for="signup-password">password</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<input id="signup-password-verification" type="password" name="password_verification" required />
|
||||||
|
<label class="placeholder" for="signup-password-verification">verify password</label>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<script>
|
<script>
|
||||||
APP.on( 'load', () => {
|
APP.on( 'load', () => {
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: x-large;
|
font-size: x-large;
|
||||||
content: "#";
|
content: "#";
|
||||||
color: var(--text);
|
color: var(--text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
#channel-list-container .channel-list > li.channel a {
|
#channel-list-container .channel-list > li.channel a {
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.1rem;
|
top: 0.1rem;
|
||||||
right: 0.1rem;
|
right: 0.1rem;
|
||||||
color: rgb(from var(--text) r g b / 0.7);
|
color: rgb(from var(--text-primary) r g b / 0.7);
|
||||||
border: none;
|
border: none;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,8 @@
|
||||||
method="POST"
|
method="POST"
|
||||||
class="post-creation-form collapsible"
|
class="post-creation-form collapsible"
|
||||||
style="
|
style="
|
||||||
margin-top: 1rem
|
position: relative;
|
||||||
|
margin-top: 1rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
transition: all 0.5s;
|
transition: all 0.5s;
|
||||||
"
|
"
|
||||||
|
|
@ -221,6 +222,7 @@
|
||||||
reset-on-submit
|
reset-on-submit
|
||||||
focus-on-submit
|
focus-on-submit
|
||||||
enter-key-submits
|
enter-key-submits
|
||||||
|
accept-speech
|
||||||
></textarea>
|
></textarea>
|
||||||
|
|
||||||
<button id="chat-send" class="primary" aria-label="Send a message">
|
<button id="chat-send" class="primary" aria-label="Send a message">
|
||||||
|
|
|
||||||
|
|
@ -27,10 +27,188 @@ Deno.test({
|
||||||
const password_hash = encodeBase64(
|
const password_hash = encodeBase64(
|
||||||
await crypto.subtle.digest('SHA-256', new TextEncoder().encode(password))
|
await crypto.subtle.digest('SHA-256', new TextEncoder().encode(password))
|
||||||
);
|
);
|
||||||
|
const password_verification_hash = encodeBase64(
|
||||||
|
await crypto.subtle.digest('SHA-256', new TextEncoder().encode(password))
|
||||||
|
);
|
||||||
|
|
||||||
const info = await get_new_user(client, {
|
const info = await get_new_user(client, {
|
||||||
username,
|
username,
|
||||||
password_hash
|
password_hash,
|
||||||
|
password_verification_hash
|
||||||
|
});
|
||||||
|
|
||||||
|
asserts.assert(info);
|
||||||
|
asserts.assert(info.user);
|
||||||
|
asserts.assert(info.session);
|
||||||
|
asserts.assert(info.headers);
|
||||||
|
|
||||||
|
const user: USER = info.user;
|
||||||
|
|
||||||
|
asserts.assertEquals(user.username, username);
|
||||||
|
|
||||||
|
await delete_user(client, info);
|
||||||
|
} finally {
|
||||||
|
if (test_server_info) {
|
||||||
|
await test_server_info?.server?.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: 'API - USERS - Create (fail on mismatched password verification hash)',
|
||||||
|
permissions: {
|
||||||
|
env: true,
|
||||||
|
read: true,
|
||||||
|
write: true,
|
||||||
|
net: true
|
||||||
|
},
|
||||||
|
fn: async () => {
|
||||||
|
let test_server_info: EPHEMERAL_SERVER | null = null;
|
||||||
|
try {
|
||||||
|
test_server_info = await get_ephemeral_listen_server();
|
||||||
|
const client: API_CLIENT = api({
|
||||||
|
prefix: '/api',
|
||||||
|
hostname: test_server_info.hostname,
|
||||||
|
port: test_server_info.port
|
||||||
|
});
|
||||||
|
|
||||||
|
const username = random_username();
|
||||||
|
const password = 'password';
|
||||||
|
const password_hash = encodeBase64(
|
||||||
|
await crypto.subtle.digest('SHA-256', new TextEncoder().encode(password))
|
||||||
|
);
|
||||||
|
const password_verification_hash = encodeBase64(
|
||||||
|
await crypto.subtle.digest('SHA-256', new TextEncoder().encode(password + '1'))
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const info = await get_new_user(client, {
|
||||||
|
username,
|
||||||
|
password_hash,
|
||||||
|
password_verification_hash
|
||||||
|
});
|
||||||
|
|
||||||
|
asserts.fail('allowed user creation with mismatched password_verification_hash')
|
||||||
|
}
|
||||||
|
catch( error ) {
|
||||||
|
asserts.assert( error );
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (test_server_info) {
|
||||||
|
await test_server_info?.server?.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: 'API - USERS - Create (mismatched password_verification)',
|
||||||
|
permissions: {
|
||||||
|
env: true,
|
||||||
|
read: true,
|
||||||
|
write: true,
|
||||||
|
net: true
|
||||||
|
},
|
||||||
|
fn: async () => {
|
||||||
|
let test_server_info: EPHEMERAL_SERVER | null = null;
|
||||||
|
try {
|
||||||
|
test_server_info = await get_ephemeral_listen_server();
|
||||||
|
const client: API_CLIENT = api({
|
||||||
|
prefix: '/api',
|
||||||
|
hostname: test_server_info.hostname,
|
||||||
|
port: test_server_info.port
|
||||||
|
});
|
||||||
|
|
||||||
|
const username = random_username();
|
||||||
|
const password = 'password';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const info = await get_new_user(client, {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
password_verification: password + '1'
|
||||||
|
});
|
||||||
|
|
||||||
|
asserts.fail( 'allowed account creation with mismatched password_verification' );
|
||||||
|
}
|
||||||
|
catch( error ) {
|
||||||
|
asserts.assert( error );
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (test_server_info) {
|
||||||
|
await test_server_info?.server?.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: 'API - USERS - Create (auto-generate password in testing)',
|
||||||
|
permissions: {
|
||||||
|
env: true,
|
||||||
|
read: true,
|
||||||
|
write: true,
|
||||||
|
net: true
|
||||||
|
},
|
||||||
|
fn: async () => {
|
||||||
|
let test_server_info: EPHEMERAL_SERVER | null = null;
|
||||||
|
try {
|
||||||
|
test_server_info = await get_ephemeral_listen_server();
|
||||||
|
const client: API_CLIENT = api({
|
||||||
|
prefix: '/api',
|
||||||
|
hostname: test_server_info.hostname,
|
||||||
|
port: test_server_info.port
|
||||||
|
});
|
||||||
|
|
||||||
|
const username = random_username();
|
||||||
|
|
||||||
|
const info = await get_new_user(client, {
|
||||||
|
username,
|
||||||
|
});
|
||||||
|
|
||||||
|
asserts.assert(info);
|
||||||
|
asserts.assert(info.user);
|
||||||
|
asserts.assert(info.session);
|
||||||
|
asserts.assert(info.headers);
|
||||||
|
|
||||||
|
const user: USER = info.user;
|
||||||
|
|
||||||
|
asserts.assertEquals(user.username, username);
|
||||||
|
|
||||||
|
await delete_user(client, info);
|
||||||
|
} finally {
|
||||||
|
if (test_server_info) {
|
||||||
|
await test_server_info?.server?.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: 'API - USERS - Create (auto-generate password_verification in testing)',
|
||||||
|
permissions: {
|
||||||
|
env: true,
|
||||||
|
read: true,
|
||||||
|
write: true,
|
||||||
|
net: true
|
||||||
|
},
|
||||||
|
fn: async () => {
|
||||||
|
let test_server_info: EPHEMERAL_SERVER | null = null;
|
||||||
|
try {
|
||||||
|
test_server_info = await get_ephemeral_listen_server();
|
||||||
|
const client: API_CLIENT = api({
|
||||||
|
prefix: '/api',
|
||||||
|
hostname: test_server_info.hostname,
|
||||||
|
port: test_server_info.port
|
||||||
|
});
|
||||||
|
|
||||||
|
const username = random_username();
|
||||||
|
const password = 'password';
|
||||||
|
|
||||||
|
const info = await get_new_user(client, {
|
||||||
|
username,
|
||||||
|
password
|
||||||
});
|
});
|
||||||
|
|
||||||
asserts.assert(info);
|
asserts.assert(info);
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,18 @@ export async function get_new_user(client: API_CLIENT, user_info?: Record<string
|
||||||
new_user_request_json.password = `${Math.round(Math.random() * 10)} - ${random_username()} ! ${random_username()}`;
|
new_user_request_json.password = `${Math.round(Math.random() * 10)} - ${random_username()} ! ${random_username()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!((typeof new_user_request_json.password_verification === 'string' && new_user_request_json.password_verification.length) ||
|
||||||
|
(typeof new_user_request_json.password_verification_hash === 'string' && new_user_request_json.password_verification_hash.length))
|
||||||
|
) {
|
||||||
|
if ( typeof new_user_request_json.password === 'string' ) {
|
||||||
|
new_user_request_json.password_verification = new_user_request_json.password;
|
||||||
|
}
|
||||||
|
else if ( typeof new_user_request_json.password_hash === 'string' ) {
|
||||||
|
new_user_request_json.password_verification_hash = new_user_request_json.password_hash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (inviting_user_info) {
|
if (inviting_user_info) {
|
||||||
const invite_code = await client.fetch(`/users/${inviting_user_info.user?.id}/invites`, {
|
const invite_code = await client.fetch(`/users/${inviting_user_info.user?.id}/invites`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,6 @@ export function require_user(
|
||||||
meta: Record<string, any>
|
meta: Record<string, any>
|
||||||
): undefined | Response {
|
): undefined | Response {
|
||||||
if (!meta.user) {
|
if (!meta.user) {
|
||||||
console.dir({
|
|
||||||
require_user: true,
|
|
||||||
meta
|
|
||||||
});
|
|
||||||
return CANNED_RESPONSES.permission_denied();
|
return CANNED_RESPONSES.permission_denied();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue