Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
193 commits
Select commit Hold shift + click to select a range
9a43270
feat(vscode): Add option to rebuild gRPC bindings
ashquarky May 16, 2025
d6a5d2d
feat(grpc): Move headers into the JSON object
ashquarky May 16, 2025
f2b6938
fix(grpc): Log the error object
ashquarky May 16, 2025
a4da331
feat(grpc): helpers for responding with error codes
ashquarky May 16, 2025
93e3ad1
feat(grpc): Work-in-progress authentication middleware
ashquarky May 16, 2025
a4f5153
chore(internal): Refactor error handling
ashquarky May 16, 2025
c695ef2
feat(internal): Implement authentication and tweak error handling more
ashquarky May 16, 2025
d9f70e9
feat(internal): Check for banned accounts
ashquarky May 16, 2025
06ab562
feat(internal): WIP posts endpoint (auth not working)
ashquarky May 16, 2025
2be4de2
fix(ui): Don't crash on promise rejection (please)
ashquarky May 24, 2025
fc8204c
fix(internal): enable posts route
ashquarky May 24, 2025
5c91b11
fix(server): Fix incorrect Express error handler
ashquarky Jun 8, 2025
952c758
feat(internal): Validate and use posts endpoint, tweak tokens handling
ashquarky Jun 8, 2025
771b649
feat(internal): Set up guest access controls per-endpoint
ashquarky Jun 8, 2025
126378b
fix(server): Force re-login if backend rejects user token
ashquarky Jun 8, 2025
9668d68
feat(ui): Support redirecting out of the login page
ashquarky Jun 8, 2025
6d8eced
fix(login): Validate redirect path
ashquarky Jun 10, 2025
70907d2
fix(api): Audit and fix schema to reflect real db
ashquarky Jun 11, 2025
83221c9
fix(login): Validation for the validation gods
ashquarky Jun 11, 2025
4b8ca00
feat(api): Rate-limiting for the ratelimiting gods
ashquarky Jun 11, 2025
299db63
feat(internal): Introduce dto contract type
ashquarky Jun 12, 2025
586e77d
feat(internal): Add explicit nulls for 404s
ashquarky Jun 12, 2025
77d331c
fix(internal): Restructure auth and guard middlewares
ashquarky Jun 12, 2025
5716ddd
Merge branch 'dev' into work/grpc
ashquarky Jun 12, 2025
eda12fb
fix(api): Tweak ratelimits
ashquarky Jun 13, 2025
e81f230
feat(internal): Stronger checks on account status + mod mode
ashquarky Jun 13, 2025
1e1f0bf
feat(internal): Show moderators removed posts
ashquarky Jun 13, 2025
dad0b14
fix(web): Provide redirect on all paths
ashquarky Jun 13, 2025
ed44758
feat(docker): Export proxy certs for 3ds
ashquarky Jun 25, 2025
6739f58
chore(util): Convert to TypeScript
ashquarky Jun 25, 2025
63bcbf2
chore(auth): Intorduce enums for token/system type, enforce service t…
ashquarky Jun 27, 2025
3e20596
Merge pull request #127 from PretendoNetwork/work/grpc
mrjvs Jun 29, 2025
c3eed3e
Merge branch 'dev' into patch/util-to-ts
mrjvs Jun 29, 2025
054c43d
fix(internal): Null safety on auth check
ashquarky Jun 30, 2025
a2406f2
chore(util): Make decodeParamPack much less clever
ashquarky Jun 30, 2025
eb044b5
chore(util): Refactor processLanguage
ashquarky Jun 30, 2025
0d03a12
fix: don't hang in resizeImage
ashquarky Jun 30, 2025
9491bb0
fix: Fix callsites for processPainting
ashquarky Jun 30, 2025
69017e2
fix: Add type annotiation for TGA
ashquarky Jun 30, 2025
1604600
locales(update): Updated Catalan locale
Barcia04 Nov 3, 2023
c156455
locales(update): Updated French locale
lemaximedu66 Nov 19, 2023
b1bf73f
locales(update): Updated Czech locale
Toonlink3000 Nov 21, 2023
13c16c9
locales(update): Updated Greek locale
PHant0MSoda Nov 24, 2023
1f71366
locales(update): Updated Asturian locale
Nov 23, 2023
1736e0f
locales(update): Updated Romanian locale
Nov 23, 2023
b262070
locales(update): Updated German locale
Nov 26, 2023
60ed035
locales(update): Updated French locale
Nov 26, 2023
10c0302
locales(update): Updated Asturian locale
Nov 27, 2023
bbcf121
locales(update): Updated Spanish locale
Barcia04 Dec 4, 2023
a581b45
locales(update): Updated French locale
Dec 3, 2023
b2a286a
locales(update): Updated Catalan locale
Dec 3, 2023
9581fef
locales(update): Updated Czech locale
Toonlink3000 Dec 2, 2023
c538b2e
locales(update): Updated Serbian locale
Dec 3, 2023
f138ba3
locales(update): Updated Spanish locale
Barcia04 Dec 7, 2023
1417ac7
locales(update): Updated Ukrainian locale
SviatUKM Jan 15, 2024
5cf821d
locales(update): Updated Spanish locale
Barcia04 Jan 23, 2024
0602e22
locales(update): Updated Catalan locale
Barcia04 Jan 23, 2024
c217fa6
locales(update): Updated French locale
Feb 3, 2024
4fc1d4d
locales(update): Updated French locale
Feb 10, 2024
583a93c
locales(add): Added Welsh locale
Feb 16, 2024
8694760
locales(update): Updated Welsh locale
Feb 16, 2024
0d1c2a3
locales(update): Updated German locale
SunnyM00n Feb 27, 2024
a3dcf81
locales(update): Updated Portuguese locale
PlayerLoler Feb 26, 2024
4ea125d
locales(update): Updated Chinese (Traditional) locale
andyajhs Mar 12, 2024
bdc12d9
locales(update): Updated Greek locale
dracula99gr Mar 13, 2024
5ee0190
locales(update): Updated Dutch locale
Mar 27, 2024
5ed028e
locales(add): Added Croatian locale
Mar 31, 2024
20da34a
locales(update): Updated Croatian locale
Mar 31, 2024
7367c0c
locales(update): Updated Korean locale
hamsterb0t Apr 3, 2024
0283d45
locales(update): Updated Spanish locale
JauCR Apr 8, 2024
bb70f36
locales(update): Updated Greek locale
dracula99gr Apr 10, 2024
7b1e97c
locales(update): Updated Greek locale
forripsys Apr 10, 2024
c4d8ced
locales(update): Updated Greek locale
dracula99gr Apr 10, 2024
03dde31
locales(add): Added French (Canada) locale
hauntii Apr 12, 2024
ed7f74d
locales(add): Added Indonesian locale
BoltOneUp Apr 12, 2024
c272706
locales(update): Updated French (Canada) locale
mrkinglouis Apr 12, 2024
372d24a
locales(update): Updated French (Canada) locale
mrkinglouis Apr 12, 2024
1a77d20
locales(update): Updated Indonesian locale
BoltOneUp Apr 12, 2024
d3efe95
locales(update): Updated Chinese (Traditional) locale
nyajiiro Apr 13, 2024
ce10030
locales(update): Updated Spanish locale
Barcia04 Apr 15, 2024
5804c33
locales(update): Updated Japanese locale
DekoKiyo Apr 14, 2024
7e2c569
locales(update): Updated French (Canada) locale
mrkinglouis Apr 15, 2024
a6562cc
locales(update): Updated Italian locale
Apr 16, 2024
b520492
locales(update): Updated French (Canada) locale
mrkinglouis Apr 15, 2024
603bc9f
locales(update): Updated French (Canada) locale
mrkinglouis Apr 16, 2024
af46801
locales(update): Updated Kazakh locale
WiiUEnthusiast Apr 17, 2024
9543613
locales(update): Updated French (Canada) locale
WiiUEnthusiast Apr 17, 2024
f832906
locales(update): Updated Chinese (Simplified) locale
espyjump Apr 20, 2024
ce53f84
locales(update): Updated Indonesian locale
BoltOneUp Apr 20, 2024
3bdb5c8
locales(update): Updated Spanish locale
Barcia04 Apr 23, 2024
29dac04
locales(add): Added Hungarian locale
vargaviktor Apr 28, 2024
4761f87
locales(update): Updated Hungarian locale
vargaviktor Apr 28, 2024
b143571
locales(update): Updated Italian locale
piosus May 1, 2024
3b40162
locales(update): Updated Spanish locale
Barcia04 May 3, 2024
fe3eaae
locales(update): Updated Dutch locale
EngineerLink May 4, 2024
89c55c9
locales(update): Updated Croatian locale
milotype May 9, 2024
0fbacfd
locales(update): Updated Croatian locale
milotype May 9, 2024
81e62fb
locales(update): Updated French (Canada) locale
RISC-VLIW May 9, 2024
fb8c14c
locales(update): Updated Croatian locale
milotype May 16, 2024
444646d
locales(add): Added Slovak locale
silver-volt4 May 23, 2024
502a348
locales(update): Updated Slovak locale
silver-volt4 May 23, 2024
21009dd
locales(update): Updated Slovak locale
May 23, 2024
e2ff14a
locales(add): Added Danish locale
SirHrVedel May 28, 2024
1e2be92
locales(update): Updated Danish locale
SirHrVedel May 28, 2024
949668f
locales(update): Updated Italian locale
LNLenost Jun 4, 2024
459afbe
locales(update): Updated French locale
elnyet Jun 17, 2024
f3b183d
locales(update): Updated Catalan locale
MLDvp Jun 20, 2024
eb0179f
locales(add): Added Latvian locale
Jul 1, 2024
26241ae
locales(update): Updated Japanese locale
DekoKiyo Jul 2, 2024
0f7c6b1
locales(update): Updated Latvian locale
Jul 1, 2024
ec3cc1a
locales(update): Updated Kazakh locale
AlgerianGerman Jul 4, 2024
daddc95
locales(update): Updated Belarusian locale
AlgerianGerman Jul 4, 2024
1665a9d
locales(update): Updated German locale
SteffoSpieler Jul 21, 2024
9ec1aee
locales(add): Added Esperanto locale
hedgehxg Jul 23, 2024
08f6e0a
locales(update): Updated Esperanto locale
hedgehxg Jul 23, 2024
72092f5
locales(update): Updated Spanish locale
hedgehxg Jul 26, 2024
b2c46a2
locales(update): Updated Esperanto locale
hedgehxg Jul 25, 2024
bedfc9a
locales(update): Updated Spanish locale
gallegonovato Jul 26, 2024
e2c2def
locales(update): Updated Esperanto locale
hedgehxg Jul 27, 2024
75e2a5e
locales(update): Updated Korean locale
Jul 31, 2024
1c364d3
locales(update): Updated Esperanto locale
hedgehxg Aug 1, 2024
9fcc39f
locales(update): Updated Spanish locale
Pro597Pretendo Aug 4, 2024
1249d59
locales(add): Added Galician locale
Pro597Pretendo Aug 5, 2024
0b6a235
locales(update): Updated Galician locale
Pro597Pretendo Aug 5, 2024
6d87010
locales(update): Updated Esperanto locale
hendrikvanvelk-bit Aug 13, 2024
6ac9e50
locales(update): Updated Esperanto locale
hendrikvanvelk-bit Aug 13, 2024
6ee0fdb
locales(update): Updated Dutch locale
hendrikvanvelk-bit Aug 14, 2024
90d7702
locales(update): Updated Dutch locale
EngineerLink Aug 14, 2024
076f6a8
locales(update): Updated Esperanto locale
hendrikvanvelk-bit Aug 14, 2024
0c0b0fd
locales(update): Updated Lithuanian locale
Miskinis-web Aug 15, 2024
dfe6751
locales(update): Updated Kazakh locale
rudinn529 Aug 15, 2024
a4bf4d8
locales(update): Updated Belarusian locale
rudinn529 Aug 15, 2024
e41fc9a
locales(update): Updated Slovak locale
rudinn529 Aug 15, 2024
669d6dc
locales(update): Updated Esperanto locale
rudinn529 Aug 15, 2024
e61b18d
locales(update): Updated Esperanto locale
hendrikvanvelk-bit Aug 18, 2024
0e7dad5
locales(update): Updated Catalan locale
EarthPenguin861 Aug 28, 2024
07796c9
locales(add): Added Hebrew locale
Tomerrv Sep 2, 2024
23cc67e
locales(update): Updated Dutch locale
hendrikvanvelk-bit Sep 12, 2024
5eec6c1
locales(update): Updated Esperanto locale
hendrikvanvelk-bit Sep 12, 2024
feccda7
locales(update): Updated Hungarian locale
vargaviktor Sep 15, 2024
df75551
locales(update): Updated Chinese (Traditional Han script) locale
Freshestocto Sep 22, 2024
b7df9e1
locales(update): Updated Lithuanian locale
Algiuxs Sep 23, 2024
5077eb5
locales(update): Updated Italian locale
Oct 2, 2024
2cb0c46
locales(update): Updated Kazakh locale
rudinn529 Oct 10, 2024
9ec6ab0
locales(update): Updated Hebrew locale
rudinn529 Oct 10, 2024
2646f3e
locales(update): Updated Spanish locale
Barcia04 Oct 31, 2024
5307641
locales(add): Added Irish locale
amynicheall Nov 13, 2024
7141c87
locales(update): Updated Irish locale
amynicheall Nov 13, 2024
30bcec1
locales(update): Updated Spanish locale
Barcia04 Nov 16, 2024
f05957e
locales(update): Updated French locale
DimitriPilot3 Dec 9, 2024
8ecf1ed
locales(update): Updated French locale
elnyet Dec 9, 2024
e6216d2
locales(update): Updated French (Canada) locale
DimitriPilot3 Dec 9, 2024
c180a6b
locales(update): Updated French locale
DimitriPilot3 Dec 10, 2024
b59cdcf
locales(update): Updated French (Canada) locale
DimitriPilot3 Dec 10, 2024
65ba74e
locales(update): Updated Spanish locale
Barcia04 Dec 13, 2024
2151ded
locales(update): Updated Italian locale
piplupness Dec 23, 2024
51f5ae0
locales(update): Updated Italian locale
Dec 23, 2024
4076b0e
locales(update): Updated Italian locale
EllieNotFound Dec 23, 2024
aa2de3c
locales(update): Updated Irish locale
amynicheall Dec 27, 2024
07e2d5f
locales(update): Updated Indonesian locale
Dec 28, 2024
66cabac
locales(update): Updated Spanish locale
Barcia04 Jan 5, 2025
35ea599
locales(update): Updated French locale
Jan 8, 2025
4b11016
locales(update): Updated Korean locale
Jan 8, 2025
1a730bf
locales(update): Updated Italian locale
EllieNotFound Jan 18, 2025
ff58ec8
locales(update): Updated Swedish locale
Attomannen Jan 21, 2025
e72b790
locales(update): Updated Serbian locale
Jan 22, 2025
a7c1a5e
locales(add): Added Tamil locale
Jan 27, 2025
444926f
locales(update): Updated Tamil locale
Jan 27, 2025
3003fce
locales(update): Updated Indonesian locale
BoltOneUp Jan 30, 2025
1652879
locales(update): Updated Ukrainian locale
UKRMa1e Feb 1, 2025
f282e05
locales(add): Added English (en@uwu) locale
BoltOneUp Feb 19, 2025
791e701
locales(update): Updated English (en@uwu) locale
BoltOneUp Feb 20, 2025
e9ec5e7
locales(update): Updated French locale
Barcia04 Mar 29, 2025
d94be6e
locales(add): Added Portuguese (Portugal) locale
SantosSi Apr 10, 2025
823e098
locales(update): Updated Portuguese (Portugal) locale
SantosSi Apr 10, 2025
800ec65
locales(update): Updated English (en@uwu) locale
wolfwolfnuke2 Apr 16, 2025
17f4673
locales(update): Updated French (Canada) locale
DimitriPilot3 Apr 23, 2025
0c4020c
locales(update): Updated French (Canada) locale
tuglife50 Apr 27, 2025
02d6d6d
locales(update): Updated French (Canada) locale
tuglife50 Apr 27, 2025
287187b
locales(update): Updated Galician locale
May 1, 2025
04a802e
locales(update): Updated Spanish locale
Barcia04 May 5, 2025
05772cb
locales(update): Updated French (Canada) locale
DimitriPilot3 May 18, 2025
ca9e99c
locales(update): Updated Japanese locale
aitan-aitan Jun 10, 2025
5d23f19
locales(update): Updated Kazakh locale
weblate Jun 30, 2025
5f6ab51
locales(update): Updated Serbian locale
weblate Jun 30, 2025
cbbf6b0
locales(update): Updated Belarusian locale
weblate Jun 30, 2025
60863e5
locales(update): Updated Welsh locale
weblate Jun 30, 2025
ad854e7
locales(update): Updated Slovak locale
weblate Jun 30, 2025
ad548f6
locales(update): Updated Danish locale
weblate Jun 30, 2025
74a56ba
locales(update): Updated Hebrew locale
weblate Jun 30, 2025
5466c41
locales(update): Updated Irish locale
weblate Jun 30, 2025
a474416
locales(update): Updated English (en@uwu) locale
weblate Jun 30, 2025
201e00b
Merge pull request #138 from PretendoNetwork/patch/util-to-ts
mrjvs Jun 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,18 @@ services:

proxy:
image: mitmproxy/mitmproxy
command: mitmweb --mode regular@8888 -s /data/mitmproxy-local.py -v --web-host 0.0.0.0 -k --set tls_version_client_min=UNBOUNDED --set tls_version_server_min=UNBOUNDED --set web_password=letmein
command: >
mitmweb -v --mode regular@8888
-s /data/mitmproxy-local.py
--web-host 0.0.0.0 --set web_password=letmein
-k --set tls_version_client_min=UNBOUNDED --set tls_version_server_min=UNBOUNDED
--set key_size=1024
ports:
- 8888:8888
- 127.0.0.1:8081:8081
volumes:
- "./mitmproxy-local.py:/data/mitmproxy-local.py"
- "./mitmproxy-data/:/home/mitmproxy/.mitmproxy/"
extra_hosts:
- "host.docker.internal:host-gateway"

Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ dist/
newman
config.json
uploads/

.docker/mitmproxy-data
7 changes: 7 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@
"env": {
"PN_JUXTAPOSITION_UI_USE_PRESETS": "docker"
}
},
{
"type": "node-terminal",
"name": "Rebuild gRPC",
"request": "launch",
"command": "npm rebuild @repo/grpc-client",
"cwd": "${workspaceFolder}"
}
],
"compounds": [
Expand Down
4 changes: 3 additions & 1 deletion apps/juxtaposition-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"ejs": "^3.1.10",
"esbuild-plugin-copy": "^2.1.1",
"express": "^4.21.2",
"express-async-errors": "^3.1.1",
"express-prom-bundle": "^7.0.2",
"express-rate-limit": "^7.5.0",
"express-session": "^1.18.1",
Expand All @@ -52,7 +53,8 @@
"redis": "^4.7.0",
"sharp": "^0.33.5",
"tga": "^1.0.7",
"tsx": "^4.19.3"
"tsx": "^4.19.3",
"zod": "^3.25.56"
},
"devDependencies": {
"@pretendonetwork/eslint-config": "^0.0.11",
Expand Down
65 changes: 65 additions & 0 deletions apps/juxtaposition-ui/src/api/post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { apiFetchUser } from '@/fetch';
import type { UserTokens } from '@/fetch';

/* !!! HEY
* This type lives in apps/miiverse-api/src/services/internal/contract/post.ts
* Modify it there and copy-paste here! */

/* This type is the contract for the frontend. If we make changes to the db, this shape should be kept. */
export type PostDto = {
id: string;
title_id?: string; // number
screen_name: string;
body: string;
app_data?: string; // nintendo base64

painting?: string; // base64 or '', undef for PMs
screenshot?: string; // URL frag (leading /) or '', undef for PMs
screenshot_length?: number;

search_key?: string[]; // can be []
topic_tag?: string; // can be ''

community_id: string; // number
created_at: string; // ISO Z
feeling_id?: number;

is_autopost: boolean;
is_community_private_autopost: boolean;
is_spoiler: boolean;
is_app_jumpable: boolean;

empathy_count: number;
country_id: number;
language_id: number;

mii: string; // nintendo base64
mii_face_url: string; // full URL (cdn., r2-cdn.)

pid: number;
platform_id?: number;
region_id?: number;
parent: string | null;

reply_count: number;
verified: boolean;

message_to_pid: string | null;
removed: boolean;
removed_by?: number;
removed_at?: string; // ISO Z
removed_reason?: string;

yeahs: number[];
};

/**
* Fetches a Post for a given ID.
* @param tokens User to perform fetch as. Responses will be according to this users' permissions (user, moderator etc.)
* @param post_id The ID of the post to get.
* @returns Post object
*/
export async function getPostById(tokens: UserTokens, post_id: string): Promise<PostDto | null> {
const post = await apiFetchUser<PostDto>(tokens, `/api/v1/posts/${post_id}`);
return post;
}
5 changes: 4 additions & 1 deletion apps/juxtaposition-ui/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const schema = z.object({
logSensitive: zodCoercedBoolean().default(false),
httpPort: z.coerce.number().default(8080),
httpCookieDomain: z.string().default('.pretendo.network'),
/** "Safe" base origin for login-redirect validation */
httpBaseUrl: z.string().default('https://juxt.pretendo.network'),
/** Configures proxy trust (X-Forwarded-For etc.). Can be `true` to unconditionally trust, or
* provide a numeric hop count, or comma-seperated CIDR ranges.
* See https://expressjs.com/en/guide/behind-proxies.html
Expand Down Expand Up @@ -94,7 +96,8 @@ export const config = {
http: {
port: unmappedConfig.httpPort,
cookieDomain: unmappedConfig.httpCookieDomain,
trustProxy: unmappedConfig.httpTrustProxy
trustProxy: unmappedConfig.httpTrustProxy,
baseUrl: unmappedConfig.httpBaseUrl
},
metrics: {
enabled: unmappedConfig.metricsEnabled,
Expand Down
43 changes: 38 additions & 5 deletions apps/juxtaposition-ui/src/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,72 @@
import { Metadata } from 'nice-grpc';
import { config } from '@/config';
import { grpcClient } from '@/grpc';
import type { PacketResponse } from '@repo/grpc-client/out/packet';

export type FetchOptions = {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
headers?: Record<string, string>;
headers?: Record<string, string | undefined>;
body?: Record<string, any> | undefined | null;
};

export interface FetchError extends Error {
name: 'FetchError';
status: number;
response: any;
}
function FetchError(response: PacketResponse, message: string): FetchError {
const error = new Error(message) as FetchError;
error.name = 'FetchError';
error.status = response.status;
error.response = response.payload; // parse json?
return error;
}

function isErrorHttpStatus(status: number): boolean {
return status >= 400 && status < 600;
}

export async function apiFetch<T>(path: string, options?: FetchOptions): Promise<T> {
export async function apiFetch<T>(path: string, options?: FetchOptions): Promise<T | null> {
const defaultedOptions = {
method: 'GET',
headers: {},
...options
};

const metadata = Metadata({
...defaultedOptions.headers,
'X-API-Key': config.grpc.miiverse.apiKey
});
const response = await grpcClient.sendPacket({
path,
method: defaultedOptions.method,
headers: JSON.stringify(defaultedOptions.headers),
payload: defaultedOptions.body ? JSON.stringify(defaultedOptions.body) : undefined
}, {
metadata
});

if (isErrorHttpStatus(response.status)) {
throw new Error(`HTTP error! status: ${response.status}`);
if (response.status === 404) {
return null;
} else if (isErrorHttpStatus(response.status)) {
throw FetchError(response, `HTTP error! status: ${response.status} ${response.payload}`);
}

return JSON.parse(response.payload) as T;
}

export type UserTokens = {
serviceToken?: string;
oauthToken?: string;
};

export async function apiFetchUser<T>(tokens: UserTokens, path: string, options?: FetchOptions): Promise<T | null> {
options = {
...options,
headers: {
'x-service-token': tokens.serviceToken,
'x-oauth-token': tokens.oauthToken,
...options?.headers
}
};
return apiFetch<T>(path, options);
}
129 changes: 129 additions & 0 deletions apps/juxtaposition-ui/src/images.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { Buffer } from 'node:buffer';
// @ts-expect-error Missing upstream types for this library
import TGA from 'tga';
// @ts-expect-error Missing upstream types for this library
import imagePixels from 'image-pixels';
import { inflate, deflate } from 'pako';
import { PNG } from 'pngjs';
import bmp from 'bmp-js';
import sharp from 'sharp';
import { logger } from '@/logger';

/** Ingests a BMP-format painting and converts it to the usual TGA format.
* @param painting base64-encoded bmp image
* @returns base64-encoded zlib-compressed TGA
*/
export async function processBmpPainting(painting: string): Promise<string | null> {
const paintingBuffer = Buffer.from(painting, 'base64');
const bitmap = bmp.decode(paintingBuffer);
const tga = createTgaFromPixelData(bitmap.width, bitmap.height, bitmap.data, false);

let output: Uint8Array;
try {
output = deflate(tga, { level: 6 });
} catch (err) {
logger.error(err, 'Could not compress painting');
return null;
}
return Buffer.from(output.buffer).toString('base64');
}

/**
* Ingests a raw painting and converts it for CDN upload.
* @param painting base64-encoded zlib-compressed TGA
* @returns PNG in buffer
*/
export async function processPainting(painting: string): Promise<Buffer | null> {
const paintingBuffer = Buffer.from(painting, 'base64');
let output: Uint8Array;
try {
output = inflate(paintingBuffer);
} catch (err) {
logger.error(err, 'Could not decompress painting');
return null;
}
let tga: TGA;
try {
tga = new TGA(Buffer.from(output.buffer));
} catch (e) {
logger.error(e, 'Could not parse painting');
return null;
}
const png = new PNG({
width: tga.width,
height: tga.height
});
png.data = tga.pixels;
return PNG.sync.write(png);
}

/**
* Rezise an image.
* @param data base64-encoded input image, most codecs OK (check Sharp docs)
* @param width Target width in pixels
* @param height Target height in pixels
* @returns New image, same codec as the input (not base64'd!)
*/
export async function resizeImage(data: string, width: number, height: number): Promise<Buffer> {
const image = Buffer.from(data, 'base64');
return sharp(image)
.resize({ height, width })
.toBuffer()
.catch((err) => {
logger.error(err, 'Could not resize image!');
throw err;
});
}

export async function getTGAFromPNG(image: Buffer): Promise<string | null> {
const pngData = await imagePixels(Buffer.from(image));
const tga = TGA.createTgaBuffer(pngData.width, pngData.height, pngData.data);
let output: Uint8Array;
try {
output = deflate(tga, { level: 6 });
} catch (err) {
logger.error(err, 'Could not decompress image');
return null;
}
return Buffer.from(output.buffer).toString('base64').trim();
}

/**
* Makes a new TGA from BGRX pixel data
* @param width Width of the data
* @param height Height of the data
* @param pixels [B1, G1, R1, X1, B2, R2, G2, X2...]
* @param dontFlipY Invert the y-axis
* @returns Buffer with TGA data
*
* Modified from https://github.com/steel1990/tga/blob/dcd2bff6536c1c75719ed4309389cd66f991d8d3/src/index.js#L16-L45
*/
function createTgaFromPixelData(width: number, height: number, pixels: Buffer, dontFlipY: boolean): Buffer {
const buffer = Buffer.alloc(18 + pixels.length);
// write header
buffer.writeInt8(0, 0);
buffer.writeInt8(0, 1);
buffer.writeInt8(2, 2);
buffer.writeInt16LE(0, 3);
buffer.writeInt16LE(0, 5);
buffer.writeInt8(0, 7);
buffer.writeInt16LE(0, 8);
buffer.writeInt16LE(0, 10);
buffer.writeInt16LE(width, 12);
buffer.writeInt16LE(height, 14);
buffer.writeInt8(32, 16);
buffer.writeInt8(8, 17);

let offset = 18;
for (let i = 0; i < height; i++) {
for (let j = 0; j < width; j++) {
const idx = ((dontFlipY ? i : height - i - 1) * width + j) * 4;
buffer.writeUInt8(pixels[idx + 1], offset++); // b
buffer.writeUInt8(pixels[idx + 2], offset++); // g
buffer.writeUInt8(pixels[idx + 3], offset++); // r
buffer.writeUInt8(255, offset++); // a
}
}

return buffer;
}
3 changes: 2 additions & 1 deletion apps/juxtaposition-ui/src/loggerHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { config } from '@/config';
import { decodeParamPack } from '@/util';
import type { SerializedRequest, SerializedResponse } from 'pino';
import type { Request } from 'express';
import type { ParamPack } from '@/types/common/param-pack';

type SerializedNintendoRequest = SerializedRequest & { param_pack?: Record<string, string> };
type SerializedNintendoRequest = SerializedRequest & { param_pack?: ParamPack };

function redactHeaders(headers: Record<string, string>, allowlist: string[]): Record<string, string> {
if (!config.log.sensitive) {
Expand Down
4 changes: 2 additions & 2 deletions apps/juxtaposition-ui/src/middleware/checkBan.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ async function checkBan(request, response, next) {
if (!accessAllowed) {
response.status(500);
if (request.directory === 'web') {
return response.render('web/login.ejs', { toast: 'No access. Must be tester or dev' });
return response.render('web/login.ejs', { toast: 'No access. Must be tester or dev', redirect: request.originalUrl });
} else {
return response.render('portal/partials/ban_notification.ejs', {
user: null,
Expand Down Expand Up @@ -66,7 +66,7 @@ async function checkBan(request, response, next) {
default:
banMessage = `${request.user.username} has been banned. \n\nIf you have any questions contact the moderators in the Discord server or forum.`;
}
return response.render('web/login.ejs', { toast: banMessage });
return response.render('web/login.ejs', { toast: banMessage, redirect: request.originalUrl });
} else {
return response.render(request.directory + '/partials/ban_notification.ejs', {
user: userSettings,
Expand Down
7 changes: 5 additions & 2 deletions apps/juxtaposition-ui/src/middleware/consoleAuth.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ async function auth(request, response, next) {
if (request.session && request.session.user && request.session.pid && !request.isWrite) {
request.user = request.session.user;
request.pid = request.session.pid;
request.tokens = request.session.tokens;
} else {
request.pid = request.headers['x-nintendo-servicetoken'] ? await util.processServiceToken(request.headers['x-nintendo-servicetoken']) : null;
request.user = request.pid ? await util.getUserDataFromPid(request.pid) : null;
request.tokens = { serviceToken: request.headers['x-nintendo-servicetoken'] };
request.pid = request.headers['x-nintendo-servicetoken'] ? await util.getPIDFromServiceToken(request.headers['x-nintendo-servicetoken']) : null;
request.user = request.pid ? await util.getUserAccountData(request.pid) : null;

request.session.user = request.user;
request.session.pid = request.pid;
request.session.tokens = request.tokens;
}

// Set headers
Expand Down
Loading