Fetch the source of a val

This val was created before the introduction of https://esm.town

Usage

curl https://pomdtr-raw.web.val.run/<author>/<name>.<extension>[?v=<version>]

To see the code of this val, use https://pomdtr-raw.web.val.run/pomdtr/raw.ts

Examples

Fetching the val code

$  curl https://pomdtr-raw.web.val.run/pomdtr/add.tsx

You can also use js, jsx and ts extension (only the content-type change, there is no transpilation).

Fetching private val

Pass an api token as an username

$ curl "https://<token>@pomdtr-raw.web.val.run/pomdtr/privateVal.ts"

Fetching the val README

$ curl https://pomdtr-raw.web.val.run/pomdtr/add.md

Getting an image

$ curl https://pomdtr-raw.web.val.run/pomdtr/add.png

Fetching a specific version of a val

$ curl https://pomdtr-raw.web.val.run/pomdtr/raw.ts?v=66

You need to be authenticated to use this method.

Fetching the val metadata

$ curl https://pomdtr-raw.web.val.run/pomdtr/add.json

Running vals locally using Deno

Create a new val.ts file referencing the @pomdtr.add

import { add } from "https://pomdtr-raw.web.val.run/pomdtr/add.ts";

console.log(add(1, 2));

then use deno run

$ deno run ./val.ts
3

If you val accept a request and return a response, you can pass it to Deno.Serve to run it locally!

import {raw} from "https://pomdtr-raw.web.val.run/pomdtr/raw.ts";

Deno.serve(raw);

If your val is private, you can set the DENO_AUTH_TOKENS env.

DENO_AUTH_TOKENS=<val-town-token>@pomdtr-raw.web.val.run
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
import { parseAuthorizationHeader } from "https://esm.town/v/pomdtr/parseAuthorizationHeader";
import { readmeToHtmlResponse } from "https://esm.town/v/pomdtr/readme";
async function createScreenshot(code: string) {
const apiUrl = "https://sourcecodeshots.com/api/image";
const resp = await fetch(apiUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ code }),
});
if (!resp.ok) {
throw new Error(await resp.text());
}
return resp.blob();
}
export default async function raw(req: Request) {
const url = new URL(req.url);
const [author, file] = url.pathname.slice(1).split("/");
if (!author || !file) {
const { author, name } = extractValInfo(import.meta.url);
return readmeToHtmlResponse(author, name);
}
const [name, extension] = file.split(".");
const headers = {};
if (req.headers.get("Authorization")) {
const auth = parseAuthorizationHeader(
req.headers.get("Authorization"),
);
if (auth.type == "basic") {
headers["Authorization"] = `Bearer ${auth.username}`;
}
else {
headers["Authorization"] = req.headers.get("Authorization");
}
}
const resp = await fetch(`https://api.val.town/v1/alias/${author}/${name}`, {
headers,
});
if (!resp.ok) {
return resp;
}
let val = await resp.json();
if (url.searchParams.has("v")) {
const version = url.searchParams.get("v");
const resp = await fetch(
`https://api.val.town/v1/vals/${val.id}/versions/${version}`,
{ headers },
);
if (!resp.ok) {
return resp;
}
val = await resp.json();
}
if (extension == "json") {
return new Response(JSON.stringify(val, null, 2), {
headers: { "Content-Type": "text/json" },
});
}
if (extension == "md") {
return new Response(val.readme, {
headers: { "content-type": "text/markdown" },
});
}
if (extension == "js") {
return new Response(val.code, {
headers: {
"Content-Type": "text/javascript",
},
});
}
if (extension == "ts") {
return new Response(val.code, {
headers: {
"Content-Type": "text/javascript",
},
});
}
if (extension == "jsx") {
return new Response(val.code, {
headers: {
"Content-Type": "text/jsx",
},
});
}
if (extension == "tsx") {
return new Response(val.code, {
headers: {
"Content-Type": "text/tsx",

jsr2gh

Redirect to jsr package's GitHub repository page, like vladimyr-jsr2gh.web.val.run/@luca/flag

Usage

https://vladimyr-jsr2gh.web.val.run/<scope>/<name>

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// SPDX-License-Identifier: 0BSD
import { serveReadme } from "https://esm.town/v/vladimyr/serveReadme";
import { toHonoHandler } from "https://esm.town/v/vladimyr/toHonoHandler";
import { Hono } from "jsr:@kyiro/hono";
import ky from "npm:ky";
import * as v from "npm:valibot";
const PackageDataSchema = v.object({
githubRepository: v.nullable(v.object({
owner: v.string(),
name: v.string(),
})),
});
const app = new Hono();
app.get("/", toHonoHandler(serveReadme(import.meta.url)));
app.get("/:scope{@[^/]+}/:name", async (c) => {
const scope = c.req.param("scope").slice(1);
const name = c.req.param("name");
const { githubRepository: repo } = await fetchPackageData(scope, name);
if (repo) {
const githubURL = new URL(`https://github.com/${repo.owner}/${repo.name}`);
return c.redirect(githubURL);
}
const jsrURL = new URL(`https://jsr.io/@${scope}/${name}`);
return c.redirect(jsrURL);
});
export default app.fetch;
export async function fetchPackageData(scope: string, name: string) {
const prefixUrl = "https://api.jsr.io/";
const data = await ky.get(`scopes/${scope}/packages/${name}`, { prefixUrl }).json();
return v.parse(PackageDataSchema, data);
}

@postpostscript/readmeManager: Edit Val Readmes With Persistent Drafts

edit this readme

image.png

image.png

Todo:

  • Upload images
  • Autosave/save without reloading page
  • Ctrl+S
  • Multiple draft versions
  • Switch to dark codemirror theme which has markdown styling
  • Allow for checking checkboxes in preview
    • View with just the preview
  • Ability to favorite vals on the Home page
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/** @jsxImportSource https://esm.sh/hono/jsx */
import { fetchPaginatedData } from "https://esm.town/v/nbbaier/fetchPaginatedData?v=49";
import { api } from "https://esm.town/v/pomdtr/api?v=12";
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo?v=26";
import { authMiddlewareCookie, type HonoEnv } from "https://esm.town/v/postpostscript/authMiddleware";
import { html } from "https://esm.town/v/postpostscript/html";
import { updateValReadme } from "https://esm.town/v/postpostscript/updateValReadme";
import { API_URL } from "https://esm.town/v/std/API_URL?v=5";
import { blob } from "https://esm.town/v/std/blob?v=11";
import { type Context, Hono } from "npm:hono";
const app = new Hono<HonoEnv>();
app.use(
"*",
authMiddlewareCookie({
optional: true,
}),
);
export const DRAFT_PREFIX = "readmeManager:draft";
const styles = `
img {
max-width: 100%;
}
`;
app.all("/:author/:name", async (c) => {
const { pathname } = new URL(c.req.url);
const author = c.req.param("author");
const name = c.req.param("name");
let { id, readme } = await api(`/v1/alias/${author}/${name}`);
const blobID = `${DRAFT_PREFIX}:${id}`;
let draft;
if (c.get("auth")) {
if (c.req.method === "POST") {
const formData = await c.req.formData();
const submitType = formData.get("submit");
if (formData.get("readme")) {
if (submitType === "publish") {
await updateValReadme(id, formData.get("readme"));
return c.redirect(`/${author}/${name}`);
} else {
await blob.setJSON(blobID, formData.get("readme"));
return c.redirect(`/${author}/${name}?draft=1`);
}
}
} else {
draft = await blob.getJSON(blobID);
}
if (c.req.query("draft") && draft) {
readme = draft;
}
}
return c.html(
<html data-bs-theme="dark">
<head>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet"
crossorigin="anonymous"
/>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.4.0/github-markdown.min.css"
rel="stylesheet"
/>
<script
type="module"
src="https://cdn.jsdelivr.net/npm/code-mirror-web-component@0.0.16/dist/code-mirror.js"
>
</script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script
type="module"
dangerouslySetInnerHTML={{
__html: html`
import debounce from 'https://esm.sh/debounce';
async function render(text) {
window.result.innerHTML = await marked.parse(text);
document.getElementById('readme-input').value = text
}
document.addEventListener("DOMContentLoaded", () => {
const $editor = document.createElement('code-mirror')
$editor.setAttribute('id', 'editor')
$editor.setAttribute('theme', 'dracula')
$editor.setAttribute('language', 'markdown')
$editor.setAttribute('code', decodeURIComponent("${encodeURIComponent(readme ?? "")}"))
$editor.setAttribute('class', 'h-100')
$editor.addEventListener("code-change", (e) => {
render(e.detail.code)
})
render($editor.getAttribute('code'));
document.getElementById('editor-col').appendChild($editor)
})

This val is supposed to be used with the val.town extension. See the extension readme for installation instructions.

1
2
3
4
5
6
7
8
9
10
import { BrowserContext } from "https://esm.town/v/pomdtr/browser";
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
export default async function ({ url }: BrowserContext) {
const { author, name } = extractValInfo(url);
await navigator.clipboard.writeText(`${author}/${name}`);
}
// #web

Use readme as website

Usage

Create valimport {article} from "https://esm.town/v/pomdtr/article" export default article()

See this val http endpoint as an example: https://pomdtr-article.web.val.run

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { api } from "https://esm.town/v/pomdtr/api";
import { gfm } from "https://esm.town/v/pomdtr/gfm";
import { html } from "https://esm.town/v/stevekrouse/html?v=5";
export function article({
val,
title,
}: {
val?: { author: string; name: string };
title?: string;
} = {}) {
return async (req: Request) => {
if (!val) {
const { hostname } = new URL(req.url);
if (!hostname.endsWith(".web.val.run")) {
return new Response(null, {
status: 500,
});
}
const subdomain = hostname.split(".")[0];
const [author, name] = subdomain.split("-");
val = { author, name };
}
if (!title) {
title = `@${val.author}/${val.name}`;
}
const { code, readme } = await api(`/v1/alias/${val.author}/${val.name}`);
return html(await gfm(readme, { title, favicon: "📝" }));
};
}
export default article();

See SQLite Explorer for information!

1
2
3
import sqlite_explorer from "https://esm.town/v/nbbaier/sqliteExplorerApp?v=64";
export default sqlite_explorer;
1
2
3
4
5
6
7
8
9
10
11
12
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
import { indieauth } from "https://esm.town/v/pomdtr/indie_auth";
const handler = () => {
return Response.json("OK");
};
const { httpEndpoint } = extractValInfo(import.meta.url);
export default indieauth(handler, {
clientID: httpEndpoint,
});

Lowdb Example

This val demonstrates the integration between valtown and lowdb.

Read the Lodash section if you want to give superpowers to your DB.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
import { LocalStoragePreset } from "npm:lowdb/browser";
type Data = {
posts: {
id: number;
title: string;
}[];
};
// use the val name as a key ("@pomdtr/testLowDB")
const { slug } = extractValInfo(import.meta.url);
// Read or initialize DB from blob
const db = await LocalStoragePreset<Data>(slug, { posts: [] });
// Edit db content using plain JavaScript (updates are automatically persisted)
await db.update(({ posts }) => posts.push({ id: posts.length + 1, title: "lowdb is awesome" }));
console.log(db.data);

Importing a readme from another readme 🤯


import Readme from "https://pomdtr-mdx_readme.web.val.run/mod.js"

1
2
3
4
5
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
import { mdx } from "https://esm.town/v/pomdtr/mdx";
const { author, name } = extractValInfo(import.meta.url);
export default mdx(author, name);

Usage:

Create valimport blobEditor from "https://esm.town/v/pomdtr/blob_editor" export default blobEditor("article.md")

You can easily protect your val behind @pomdtr/passwordAuth or @pomdtr/emailAuth

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/** @jsxImportSource npm:hono/jsx **/
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
import { html } from "https://esm.town/v/pomdtr/gfm";
import { blob } from "https://esm.town/v/std/blob?v=11";
import { Hono } from "npm:hono";
import { HTTPException } from "npm:hono/http-exception";
import { jsxRenderer, useRequestContext } from "npm:hono/jsx-renderer";
export function blobEditor(key: string, options?: { title?: string }) {
const router = new Hono();
router.use(jsxRenderer(({ children }) => {
const c = useRequestContext();
const { pathname } = new URL(c.req.url);
return (
<html>
<head>
<link rel="icon" href="https://fav.farm/📃" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
/>
</head>
<body>
<header class="container">
<nav>
<ul>
<li>
<a href={"/"}>
<strong>{options?.title || "Blob Editor"}</strong>
</a>
</li>
</ul>
{pathname == "/"
? (
<ul>
<li>
<a href={"/edit"} role="button" class="outline">Edit Blob</a>
</li>
</ul>
)
: undefined}
</nav>
</header>
<main class="container">
{children}
</main>
</body>
</html>
);
}));
router.get("/", async (c) => {
if (key.endsWith(".md")) {
const markdown = await readBlob(key);
return c.render(
<article dangerouslySetInnerHTML={{ __html: await html(markdown) }}>
</article>,
);
}
const text = await readBlob(key);
return c.text(text);
});
router.get("/edit", async (c) => {
const text = readBlob(key);
return c.render(
<main class="container">
<form method="POST" action="/">
<article>
<textarea name="content" id="editor" style={{ height: "60vh" }}>{text}</textarea>
<footer>
<input type="submit" value="Save" />
</footer>
</article>
</form>
</main>,
);
});
router.post("/", async (c) => {
const body = await c.req.parseBody();
if (typeof body.content != "string") {
throw new HTTPException(400);
}
await blob.set(key, body.content);
return c.redirect("/");
});
return router.fetch;
}
async function readBlob(key: string) {
const resp = await blob.get(key);
if (resp.status == 200) {
return resp.text();
} else if (resp.status == 404) {
return "";
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
import { migrate, Migrations, undo } from "https://esm.town/v/saolsen/sqlite_migrations";
const { name } = extractValInfo(import.meta.url);
const MIGRATIONS: Migrations = {
name,
migrations: [
{
name: `example_user table`,
up: `create table example_user (
id integer primary key autoincrement,
username text not null
) strict`,
down: `drop table example_user`,
},
{
name: `example_user username index`,
up: `create unique index idx_example_user_username on example_user (username)`,
down: `drop index idx_example_user_username`,
},
// Try running once to see the above created.
// Then try running again to see that they don't run again since they were already run.
// Then uncomment the below migration and run again to see that only the last one is run.
// {
// name: `another table`,
// up: `create table another_table (
// id integer primary key autoincrement
// ) strict`,
// down: `drop table another_table`,
// },
],
};
await migrate(MIGRATIONS);
// await undo(MIGRATIONS);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
import { valItem } from "https://esm.town/v/pomdtr/tree_val";
const { htmlUrl } = extractValInfo(import.meta.url);
export default {
title: "🏷️ Tags",
expanded: true,
actions: [
{
type: "open",
icon: "✏️",
url: htmlUrl,
},
],
children: [{
children: "/tags/example",
title: "#example",
}, {
children: "/tags/blog",
title: "#blog",
}, {
children: "/tags/web",
title: "#web",
}],
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { blobActions } from "https://esm.town/v/pomdtr/blob_actions";
import { ActionItem, defineCommand } from "https://esm.town/v/pomdtr/cmdk";
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
import { blob } from "https://esm.town/v/std/blob?v=12";
const { slug } = extractValInfo(import.meta.url);
export const actions: ActionItem[] = [
{
title: "List Blobs",
type: "push",
push: {
command: slug,
},
},
];
export default defineCommand(async () => {
const blobs = await blob.list();
return {
type: "list",
list: {
items: blobs.map(blob => ({
title: blob.key,
actions: [
...blobActions(blob.key),
{
title: "Create Blob",
type: "push",
push: {
command: "pomdtr/create_blob",
},
},
],
})),
},
};
});

export const title = "mdx"

{title}

Usage

Create valimport { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo"; import { mdx } from "https://esm.town/v/pomdtr/mdx"; const { author, name } = extractValInfo(import.meta.url); export default mdx(author, name);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
/** @jsxImportSource https://esm.sh/preact **/
import { modifyFetchHandler } from "https://esm.town/v/andreterron/codeOnValTown?v=50";
import { api } from "https://esm.town/v/pomdtr/api";
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
import { gfm } from "https://esm.town/v/pomdtr/gfm";
import { compile } from "npm:@mdx-js/mdx";
import { render } from "npm:preact-render-to-string";
const css = `
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
`;
export function mdx(author: string, name: string) {
return async (req: Request) => {
const { readme } = await api(`/v1/alias/${author}/${name}`);
const vfile = await compile(readme, {
jsxImportSource: "https://esm.sh/preact",
});
const url = new URL(req.url);
if (url.pathname == "/mod.js") {
return new Response(vfile.toString(), {
headers: {
"Content-Type": "text/javascript",
},
});
}
const blob = new Blob([vfile.toString()], {
type: "text/tsx",
});
const importURL = URL.createObjectURL(blob);
const { default: MDXContent } = await import(importURL);
return new Response(
render(
<html>
<head>
<title>{`${author}/${name}`}</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link href="📝" rel="icon" />
<link
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.4.0/github-markdown.min.css"
rel="stylesheet"
/>
<link href="https://cdn.jsdelivr.net/npm/prismjs@v1.29.0/themes/prism.css" rel="stylesheet" />
<style dangerouslySetInnerHTML={{ __html: css }}></style>
</head>
<body class="markdown-body">
<MDXContent />
<script src="https://cdn.jsdelivr.net/npm/prismjs@v1.29.0/components/prism-core.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@v1.29.0/plugins/autoloader/prism-autoloader.min.js">
</script>
</body>
</html>,
),
{
headers: {
"Content-Type": "text/html",
},
},
);
};
}
const { author, name } = extractValInfo(import.meta.url);
export default mdx(author, name);

Authentication middleware

Guards your public http vals behind a login page.

c2a79825e9d89429014a036c29887c670806ee3f0188e01cde09adad193a6407.png

This val use a json web token stored as an http-only cookie to persist authentication.

Usage

Set an AUTH_SECRET_KEY env variable (used to sign/verify jwt tokens). Use an API token to authenticate.

import { auth } from "https://esm.town/v/pomdtr/auth_middleware";

async function handler(req: Request): Promise<Response> {
  return new Response("You are authenticated!");
}

export default auth(handler);

See @pomdtr/test_auth for an example

⚠️ Make sure to only provides your api token to vals you trust (i.e. your own), as it gives access to your whole account.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import * as cookie from "https://deno.land/std/http/cookie.ts";
import { extractValInfo } from "https://esm.town/v/pomdtr/extractValInfo";
import { callerRef } from "https://esm.town/v/pomdtr/refs";
import { sql } from "https://esm.town/v/pomdtr/sql";
import { sqlite } from "https://esm.town/v/std/sqlite?v=4";
import { html } from "https://esm.town/v/stevekrouse/html?v=5";
import process from "node:process";
import jwt from "npm:jsonwebtoken";
const cookieName = "user-jwt";
const loginPage = `<html>
<head>
<link rel="icon" href="https://fav.farm/🔒" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
</head>
<body>
<main class="container">
<article>
<form method="post">
<label for="token">API Token</label>
<input id="token" name="token" type="password" />
<button type="submit">Submit</button>
</form>
</article>
</main>
</body>
</html>`;
export function redirect(location: string): Response {
return new Response(null, {
headers: {
location,
},
status: 302,
});
}
function generateAccessToken(user) {
return jwt.sign(user, process.env.ACCESS_TOKEN_SECRET, { expiresIn: "1800s" });
}
type User = {
id: string;
username: string;
};
async function fetchUser(token: string): Promise<User> {
const resp = await fetch("https://api.val.town/v1/me", {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (resp.status !== 200) {
throw new Error("Could not fetch user");
}
return resp.json();
}
function signout() {
const res = redirect("/");
cookie.setCookie(res.headers, {
name: "user-jwt",
value: "",
httpOnly: true,
secure: true,
sameSite: "None",
maxAge: 0,
});
return res;
}
export function auth(handler) {
const secretKey = Deno.env.get("AUTH_SECRET_KEY");
if (!secretKey) {
throw new Error("env var AUTH_SECRET_KEY not set");
}
return async (req: Request) => {
const { pathname, hostname, origin } = new URL(req.url);
const val = {
username: `@${hostname.split("-")[0]}`,
};
if (pathname == "/signin" && req.method == "GET") {
return html(loginPage);
}
if (pathname == "/signin" && req.method == "POST") {
const formData = await req.formData();
const token = formData.get("token").toString();
const user = await fetchUser(token);
if (user.username != val.username) {
return new Response("Unauthorized", {
status: 403,
});
}
const webToken = jwt.sign(user, secretKey, { expiresIn: "7d" });