Compare commits

..

No commits in common. "86fa2b6d4b5305f982b071660f7e01324dd2b1e8" and "2d121048757106aca833f6e8a8775cb54612d68e" have entirely different histories.

18 changed files with 1400 additions and 1637 deletions

View file

@ -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.19", "@std/assert": "jsr:@std/assert@^1.0.17",
"@std/encoding": "jsr:@std/encoding@^1.0.10", "@std/encoding": "jsr:@std/encoding@^1.0.10",
"@std/fs": "jsr:@std/fs@^1.0.23", "@std/fs": "jsr:@std/fs@^1.0.22",
"@std/http": "jsr:@std/http@^1.0.25", "@std/http": "jsr:@std/http@^1.0.23",
"@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
View file

@ -6,27 +6,28 @@
"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.19": "1.0.19", "jsr:@std/assert@^1.0.17": "1.0.17",
"jsr:@std/cli@^1.0.19": "1.0.28", "jsr:@std/cli@^1.0.19": "1.0.25",
"jsr:@std/cli@^1.0.20": "1.0.28", "jsr:@std/cli@^1.0.20": "1.0.25",
"jsr:@std/cli@^1.0.21": "1.0.28", "jsr:@std/cli@^1.0.21": "1.0.25",
"jsr:@std/cli@^1.0.28": "1.0.28", "jsr:@std/cli@^1.0.25": "1.0.25",
"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.9", "jsr:@std/fmt@^1.0.6": "1.0.8",
"jsr:@std/fmt@^1.0.9": "1.0.9", "jsr:@std/fmt@^1.0.8": "1.0.8",
"jsr:@std/fs@^1.0.18": "1.0.23", "jsr:@std/fs@^1.0.18": "1.0.22",
"jsr:@std/fs@^1.0.19": "1.0.23", "jsr:@std/fs@^1.0.19": "1.0.22",
"jsr:@std/fs@^1.0.23": "1.0.23", "jsr:@std/fs@^1.0.21": "1.0.22",
"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.25", "jsr:@std/http@^1.0.20": "1.0.23",
"jsr:@std/http@^1.0.25": "1.0.25", "jsr:@std/http@^1.0.23": "1.0.23",
"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.17": "1.0.17", "jsr:@std/streams@^1.0.16": "1.0.16",
"npm:@types/node@*": "22.15.15" "npm:@types/node@*": "22.15.15"
}, },
"jsr": { "jsr": {
@ -58,23 +59,23 @@
"@da/bcrypt@1.0.1": { "@da/bcrypt@1.0.1": {
"integrity": "d2172d3acbcff52e0465557a1a48b1ff1c92df08c90712dae5372255a8c45eb3" "integrity": "d2172d3acbcff52e0465557a1a48b1ff1c92df08c90712dae5372255a8c45eb3"
}, },
"@std/assert@1.0.19": { "@std/assert@1.0.17": {
"integrity": "eaada96ee120cb980bc47e040f82814d786fe8162ecc53c91d8df60b8755991e", "integrity": "df5ebfffe77c03b3fa1401e11c762cc8f603d51021c56c4d15a8c7ab45e90dbe",
"dependencies": [ "dependencies": [
"jsr:@std/internal" "jsr:@std/internal"
] ]
}, },
"@std/cli@1.0.28": { "@std/cli@1.0.25": {
"integrity": "74ef9b976db59ca6b23a5283469c9072be6276853807a83ec6c7ce412135c70a" "integrity": "1f85051b370c97a7a9dfc6ba626e7ed57a91bea8c081597276d1e78d929d8c91"
}, },
"@std/encoding@1.0.10": { "@std/encoding@1.0.10": {
"integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1"
}, },
"@std/fmt@1.0.9": { "@std/fmt@1.0.8": {
"integrity": "2487343e8899fb2be5d0e3d35013e54477ada198854e52dd05ed0422eddcabe0" "integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7"
}, },
"@std/fs@1.0.23": { "@std/fs@1.0.22": {
"integrity": "3ecbae4ce4fee03b180fa710caff36bb5adb66631c46a6460aaad49515565a37", "integrity": "de0f277a58a867147a8a01bc1b181d0dfa80bfddba8c9cf2bacd6747bcec9308",
"dependencies": [ "dependencies": [
"jsr:@std/internal", "jsr:@std/internal",
"jsr:@std/path@^1.1.4" "jsr:@std/path@^1.1.4"
@ -83,13 +84,13 @@
"@std/html@1.0.5": { "@std/html@1.0.5": {
"integrity": "4e2d693f474cae8c16a920fa5e15a3b72267b94b84667f11a50c6dd1cb18d35e" "integrity": "4e2d693f474cae8c16a920fa5e15a3b72267b94b84667f11a50c6dd1cb18d35e"
}, },
"@std/http@1.0.25": { "@std/http@1.0.23": {
"integrity": "577b4252290af1097132812b339fffdd55fb0f4aeb98ff11bdbf67998aa17193", "integrity": "6634e9e034c589bf35101c1b5ee5bbf052a5987abca20f903e58bdba85c80dee",
"dependencies": [ "dependencies": [
"jsr:@std/cli@^1.0.28", "jsr:@std/cli@^1.0.25",
"jsr:@std/encoding", "jsr:@std/encoding",
"jsr:@std/fmt@^1.0.9", "jsr:@std/fmt@^1.0.8",
"jsr:@std/fs@^1.0.23", "jsr:@std/fs@^1.0.21",
"jsr:@std/html", "jsr:@std/html",
"jsr:@std/media-types", "jsr:@std/media-types",
"jsr:@std/net", "jsr:@std/net",
@ -112,8 +113,8 @@
"jsr:@std/internal" "jsr:@std/internal"
] ]
}, },
"@std/streams@1.0.17": { "@std/streams@1.0.16": {
"integrity": "7859f3d9deed83cf4b41f19223d4a67661b3d3819e9fc117698f493bf5992140" "integrity": "85030627befb1767c60d4f65cb30fa2f94af1d6ee6e5b2515b76157a542e89c4"
} }
}, },
"npm": { "npm": {
@ -136,10 +137,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.19", "jsr:@std/assert@^1.0.17",
"jsr:@std/encoding@^1.0.10", "jsr:@std/encoding@^1.0.10",
"jsr:@std/fs@^1.0.23", "jsr:@std/fs@^1.0.22",
"jsr:@std/http@^1.0.25", "jsr:@std/http@^1.0.23",
"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"
] ]

View file

@ -41,6 +41,13 @@ 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;
} }

View file

@ -139,22 +139,6 @@ 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

File diff suppressed because it is too large Load diff

6
public/foo/index.html Normal file
View file

@ -0,0 +1,6 @@
<!DOCTYPE html>
<html>
<body>
<h1>Hello World - foo</h1>
</body>
</html>

File diff suppressed because it is too large Load diff

View file

@ -10,7 +10,6 @@
<link rel="icon" type="image/png" sizes="16x16" href="/icons/favicon-16x16.png" ></link> <link rel="icon" type="image/png" sizes="16x16" href="/icons/favicon-16x16.png" ></link>
<link rel="stylesheet" href="/base.css"></link> <link rel="stylesheet" href="/base.css"></link>
<link rel="stylesheet" href="/icons.css"></link>
<link rel="stylesheet" href="/files/custom.css"></link> <link rel="stylesheet" href="/files/custom.css"></link>
<!-- inlining these to force them to be scoped for everything else --> <!-- inlining these to force them to be scoped for everything else -->

View file

@ -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;" allow="fullscreen; picture-in-picture; clipboard-write; encrypted-media; web-share"
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>

View file

@ -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;" allow="clipboard-write; encrypted-media; picture-in-picture; web-share;"
referrerpolicy="strict-origin-when-cross-origin" referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen allowfullscreen
loading="lazy" loading="lazy"

View file

@ -145,7 +145,7 @@
padding: 1rem; padding: 1rem;
display: flex; display: flex;
align-items: center; align-items: center;
color: var(--text-primary); color: var(--text);
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-primary); color: var(--text);
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: 1rem; padding: 0.75rem;
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: 1rem 2rem; padding: 0.75rem;
} }
</style> </style>
<div id="server-info"> <div id="server-info">

View file

@ -17,9 +17,19 @@
} }
#auth-container { #auth-container {
width: 95%;
max-height: calc(min(90vh,900px));
border-radius: 10px;
position: relative; position: relative;
padding: 8em 4em; background: var(--bg-lighter);
max-width: 40em;
margin: 0 auto;
border-radius: calc( var(--border-radius) * 2.5);
padding: 2em 1em;
overflow-y: scroll; overflow-y: scroll;
transition: all 0.33s ease;
animation: zoomsettle 0.4s ease;
} }
#signup-tab, #signup-tab,
@ -98,10 +108,6 @@
<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', () => {

View file

@ -159,7 +159,7 @@
font-weight: bold; font-weight: bold;
font-size: x-large; font-size: x-large;
content: "#"; content: "#";
color: var(--text-primary); color: var(--text);
} }
#channel-list-container .channel-list > li.channel a { #channel-list-container .channel-list > li.channel a {

View file

@ -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-primary) r g b / 0.7); color: rgb(from var(--text) r g b / 0.7);
border: none; border: none;
z-index: 10; z-index: 10;
} }

View file

@ -152,8 +152,7 @@
method="POST" method="POST"
class="post-creation-form collapsible" class="post-creation-form collapsible"
style=" style="
position: relative; margin-top: 1rem
margin-top: 1rem;
width: 100%; width: 100%;
transition: all 0.5s; transition: all 0.5s;
" "
@ -222,7 +221,6 @@
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">

View file

@ -27,188 +27,10 @@ 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);

View file

@ -119,18 +119,6 @@ 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',

View file

@ -36,6 +36,10 @@ 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();
} }
} }