Merge remote-tracking branch 'origin/main' into pr/1131 and deno task fmt

pull/1131/head
Zef Hemel 2024-10-28 14:15:36 +01:00
commit ce5992719c
9 changed files with 113 additions and 75 deletions

View File

@ -5,7 +5,7 @@ import {
unregisterServiceWorkers,
} from "../sw_util.ts";
import { encodePageURI } from "@silverbulletmd/silverbullet/lib/page_ref";
import { urlPrefix, toRealUrl } from "../../lib/url_hack.ts";
import { toRealUrl, urlPrefix } from "../../lib/url_hack.ts";
const defaultFetchTimeout = 30000; // 30 seconds
@ -69,7 +69,9 @@ export class HttpSpacePrimitives implements SpacePrimitives {
"Received an authentication redirect, redirecting to URL: " +
redirectHeader,
);
location.href = redirectHeader.startsWith(urlPrefix) ? redirectHeader : toRealUrl(redirectHeader);
location.href = redirectHeader.startsWith(urlPrefix)
? redirectHeader
: toRealUrl(redirectHeader);
throw new Error("Redirected");
} else {
console.error("Got a redirect status but no location header", result);
@ -84,7 +86,9 @@ export class HttpSpacePrimitives implements SpacePrimitives {
result.url,
);
alert("You are not authenticated, redirecting to: " + redirectHeader);
location.href = redirectHeader.startsWith(urlPrefix) ? redirectHeader : toRealUrl(redirectHeader);
location.href = redirectHeader.startsWith(urlPrefix)
? redirectHeader
: toRealUrl(redirectHeader);
throw new Error("Not authenticated");
} else {
// If not, let's reload

View File

@ -1,50 +1,48 @@
export const urlPrefix : string = Deno.env.get('SB_URL_PREFIX') ?? (globalThis.silverBulletConfig ? globalThis.silverBulletConfig.urlPrefix : null) ?? '';
export const urlPrefix: string = Deno.env.get("SB_URL_PREFIX") ??
(globalThis.silverBulletConfig
? globalThis.silverBulletConfig.urlPrefix
: null) ??
"";
export const toRealUrl = <T extends (string | URL)>(url : T) : T => {
if (typeof url === 'string') {
const stringUrl = url as string;
if (stringUrl.startsWith('http://') || stringUrl.startsWith('https://')) {
const parsedUrl = new URL(stringUrl);
parsedUrl.pathname = urlPrefix + parsedUrl.pathname;
//console.log("Converted ", url, parsedUrl.href)
return String(parsedUrl.href) as T;
}
else {
if (!stringUrl.startsWith('/')) {
console.log("Don't know how to deal with relative path: ", url);
}
//console.log("Converted ", url, urlPrefix + stringUrl)
return (urlPrefix + stringUrl) as T;
}
}
else if (url.protocol === 'http:' || url.protocol === 'https:') {
const parsedUrl = new URL(url as URL);
parsedUrl.pathname = urlPrefix + parsedUrl.pathname;
//console.log("Converted ", url, parsedUrl)
return parsedUrl as T;
}
else {
return url;
export const toRealUrl = <T extends (string | URL)>(url: T): T => {
if (typeof url === "string") {
const stringUrl = url as string;
if (stringUrl.startsWith("http://") || stringUrl.startsWith("https://")) {
const parsedUrl = new URL(stringUrl);
parsedUrl.pathname = urlPrefix + parsedUrl.pathname;
//console.log("Converted ", url, parsedUrl.href)
return String(parsedUrl.href) as T;
} else {
if (!stringUrl.startsWith("/")) {
console.log("Don't know how to deal with relative path: ", url);
}
//console.log("Converted ", url, urlPrefix + stringUrl)
return (urlPrefix + stringUrl) as T;
}
} else if (url.protocol === "http:" || url.protocol === "https:") {
const parsedUrl = new URL(url as URL);
parsedUrl.pathname = urlPrefix + parsedUrl.pathname;
//console.log("Converted ", url, parsedUrl)
return parsedUrl as T;
} else {
return url;
}
};
export const toInternalUrl = (url : string) => {
if (url.startsWith('http://') || url.startsWith('https://')) {
const parsedUrl = new URL(url);
if (parsedUrl.pathname.startsWith(urlPrefix)) {
parsedUrl.pathname = parsedUrl.pathname.substr(urlPrefix.length);
return parsedUrl.href;
}
else {
console.log("Don't know how to deal with non-prefix: ", url);
return url;
}
} else if (url.startsWith(urlPrefix)) {
return url.substr(urlPrefix.length);
}
else {
console.log("Don't know how to deal with non-prefix: ", url);
return url;
export const toInternalUrl = (url: string) => {
if (url.startsWith("http://") || url.startsWith("https://")) {
const parsedUrl = new URL(url);
if (parsedUrl.pathname.startsWith(urlPrefix)) {
parsedUrl.pathname = parsedUrl.pathname.substr(urlPrefix.length);
return parsedUrl.href;
} else {
console.log("Don't know how to deal with non-prefix: ", url);
return url;
}
} else if (url.startsWith(urlPrefix)) {
return url.substr(urlPrefix.length);
} else {
console.log("Don't know how to deal with non-prefix: ", url);
return url;
}
};

View File

@ -60,12 +60,13 @@ export class JWTIssuer {
createJWT(
payload: Record<string, unknown>,
expirySeconds: number,
expirySeconds?: number,
): Promise<string> {
return create({ alg: "HS512", typ: "JWT" }, {
...payload,
exp: getNumericDate(expirySeconds),
}, this.key);
const jwtPayload = { ...payload };
if (expirySeconds) {
jwtPayload.exp = getNumericDate(expirySeconds);
}
return create({ alg: "HS512", typ: "JWT" }, jwtPayload, this.key);
}
verifyAndDecodeJWT(jwt: string): Promise<Record<string, unknown>> {

View File

@ -20,7 +20,7 @@ import {
parsePageRef,
} from "@silverbulletmd/silverbullet/lib/page_ref";
import { base64Encode } from "$lib/crypto.ts";
import { urlPrefix, toRealUrl, toInternalUrl } from "$lib/url_hack.ts";
import { toInternalUrl, toRealUrl, urlPrefix } from "$lib/url_hack.ts";
const authenticationExpirySeconds = 60 * 60 * 24 * 7; // 1 week
@ -105,7 +105,7 @@ export class HttpServer {
spaceServer.pagesPath.replaceAll("\\", "\\\\"),
).replaceAll(
"{{URL_PREFIX}}",
urlPrefix
urlPrefix,
)
.replace(
"{{DESCRIPTION}}",
@ -323,36 +323,39 @@ export class HttpServer {
this.app.get("/.logout", (c) => {
const url = new URL(toInternalUrl(c.req.url));
deleteCookie(c, authCookieName(url.host));
deleteCookie(c, "refreshLogin");
return c.redirect(toRealUrl("/.auth"));
});
this.app.get("/.auth", (c) => {
const html = this.clientAssetBundle.readTextFileSync(".client/auth.html")
.replaceAll(
"{{URL_PREFIX}}",
urlPrefix
);
.replaceAll(
"{{URL_PREFIX}}",
urlPrefix,
);
return c.html(html);
}).post(
validator("form", (value, c) => {
const username = value["username"];
const password = value["password"];
const rememberMe = value["rememberMe"];
if (
!username || typeof username !== "string" ||
!password || typeof password !== "string"
!password || typeof password !== "string" ||
(rememberMe && typeof rememberMe !== "string")
) {
return c.redirect(toRealUrl("/.auth?error=0"));
}
return { username, password };
return { username, password, rememberMe };
}),
async (c) => {
const req = c.req;
const url = new URL(toInternalUrl(c.req.url));
const { username, password } = req.valid("form");
const url = new URL(c.req.url);
const { username, password, rememberMe } = req.valid("form");
const {
user: expectedUser,
@ -361,18 +364,24 @@ export class HttpServer {
if (username === expectedUser && password === expectedPassword) {
// Generate a JWT and set it as a cookie
const jwt = await this.spaceServer.jwtIssuer.createJWT(
{ username },
authenticationExpirySeconds,
);
const jwt = rememberMe
? await this.spaceServer.jwtIssuer.createJWT({ username })
: await this.spaceServer.jwtIssuer.createJWT(
{ username },
authenticationExpirySeconds,
);
console.log("Successful auth");
const inAWeek = new Date(
Date.now() + authenticationExpirySeconds * 1000,
);
setCookie(c, authCookieName(url.host), jwt, {
expires: new Date(
Date.now() + authenticationExpirySeconds * 1000,
), // in a week
expires: inAWeek,
// sameSite: "Strict",
// httpOnly: true,
});
if (rememberMe) {
setCookie(c, "refreshLogin", "true", { expires: inAWeek });
}
const values = await c.req.parseBody();
const from = values["from"];
const result = toRealUrl(typeof from === "string" ? from : "/");
@ -380,7 +389,7 @@ export class HttpServer {
return c.redirect(result);
} else {
console.error("Authentication failed, redirecting to auth page.");
return c.redirect(toRealUrl("/.auth?error=1"), 401);
return c.redirect("/.auth?error=1", 401);
}
},
).all((c) => {
@ -414,6 +423,7 @@ export class HttpServer {
const authToken = authHeader.slice("Bearer ".length);
if (authToken === this.spaceServer.authToken) {
// All good, let's proceed
this.refreshLogin(c, host);
return next();
} else {
console.log(
@ -445,10 +455,28 @@ export class HttpServer {
return redirectToAuth();
}
}
this.refreshLogin(c, host);
return next();
});
}
private refreshLogin(c: Context, host: string) {
if (getCookie(c, "refreshLogin")) {
const inAWeek = new Date(
Date.now() + authenticationExpirySeconds * 1000,
);
const jwt = getCookie(c, authCookieName(host));
if (jwt) {
setCookie(c, authCookieName(host), jwt, {
expires: inAWeek,
// sameSite: "Strict",
// httpOnly: true,
});
setCookie(c, "refreshLogin", "true", { expires: inAWeek });
}
}
}
private addFsRoutes() {
this.app.use(
"*",

View File

@ -69,7 +69,10 @@
<body>
<header>
<h1>
Login to <img src="{{URL_PREFIX}}/.client/logo.png" style="height: 1ch" />
Login to <img
src="{{URL_PREFIX}}/.client/logo.png"
style="height: 1ch"
/>
SilverBullet
</h1>
</header>
@ -95,6 +98,10 @@
placeholder="Password"
/>
</div>
<div>
<input type="checkbox" name="rememberMe" id="rememberMe" />
<label>Remember me</label>
</div>
<div>
<input type="submit" value="Login" />
</div>

View File

@ -88,7 +88,7 @@ declare global {
syncOnly: boolean;
readOnly: boolean;
enableSpaceScript: boolean;
urlPrefix : string;
urlPrefix: string;
};
// deno-lint-ignore no-var
var client: Client;

View File

@ -109,4 +109,4 @@ function loadJsByUrl(url,integrity=null) {
<body>
</body>
</html>`;
</html>`;

View File

@ -7,7 +7,7 @@ import type { Client } from "./client.ts";
import { cleanPageRef } from "@silverbulletmd/silverbullet/lib/resolve";
import { renderTheTemplate } from "$common/syscalls/template.ts";
import { safeRun } from "../lib/async.ts";
import { toRealUrl, toInternalUrl } from "../lib/url_hack.ts";
import { toInternalUrl, toRealUrl } from "../lib/url_hack.ts";
export type PageState = PageRef & {
scrollTop?: number;

View File

@ -16,7 +16,7 @@ import type { UploadFile } from "../../plug-api/types.ts";
import type { PageRef } from "@silverbulletmd/silverbullet/lib/page_ref";
import { openSearchPanel } from "@codemirror/search";
import { diffAndPrepareChanges } from "../cm_util.ts";
import { toRealUrl, toInternalUrl } from "../../lib/url_hack.ts";
import { toInternalUrl, toRealUrl } from "../../lib/url_hack.ts";
export function editorSyscalls(client: Client): SysCallMapping {
const syscalls: SysCallMapping = {