Merge remote-tracking branch 'origin/main' into pr/1131 and deno task fmt
commit
ce5992719c
|
@ -5,7 +5,7 @@ import {
|
||||||
unregisterServiceWorkers,
|
unregisterServiceWorkers,
|
||||||
} from "../sw_util.ts";
|
} from "../sw_util.ts";
|
||||||
import { encodePageURI } from "@silverbulletmd/silverbullet/lib/page_ref";
|
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
|
const defaultFetchTimeout = 30000; // 30 seconds
|
||||||
|
|
||||||
|
@ -69,7 +69,9 @@ export class HttpSpacePrimitives implements SpacePrimitives {
|
||||||
"Received an authentication redirect, redirecting to URL: " +
|
"Received an authentication redirect, redirecting to URL: " +
|
||||||
redirectHeader,
|
redirectHeader,
|
||||||
);
|
);
|
||||||
location.href = redirectHeader.startsWith(urlPrefix) ? redirectHeader : toRealUrl(redirectHeader);
|
location.href = redirectHeader.startsWith(urlPrefix)
|
||||||
|
? redirectHeader
|
||||||
|
: toRealUrl(redirectHeader);
|
||||||
throw new Error("Redirected");
|
throw new Error("Redirected");
|
||||||
} else {
|
} else {
|
||||||
console.error("Got a redirect status but no location header", result);
|
console.error("Got a redirect status but no location header", result);
|
||||||
|
@ -84,7 +86,9 @@ export class HttpSpacePrimitives implements SpacePrimitives {
|
||||||
result.url,
|
result.url,
|
||||||
);
|
);
|
||||||
alert("You are not authenticated, redirecting to: " + redirectHeader);
|
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");
|
throw new Error("Not authenticated");
|
||||||
} else {
|
} else {
|
||||||
// If not, let's reload
|
// If not, let's reload
|
||||||
|
|
|
@ -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 => {
|
export const toRealUrl = <T extends (string | URL)>(url: T): T => {
|
||||||
if (typeof url === 'string') {
|
if (typeof url === "string") {
|
||||||
const stringUrl = url as string;
|
const stringUrl = url as string;
|
||||||
if (stringUrl.startsWith('http://') || stringUrl.startsWith('https://')) {
|
if (stringUrl.startsWith("http://") || stringUrl.startsWith("https://")) {
|
||||||
const parsedUrl = new URL(stringUrl);
|
const parsedUrl = new URL(stringUrl);
|
||||||
parsedUrl.pathname = urlPrefix + parsedUrl.pathname;
|
parsedUrl.pathname = urlPrefix + parsedUrl.pathname;
|
||||||
//console.log("Converted ", url, parsedUrl.href)
|
//console.log("Converted ", url, parsedUrl.href)
|
||||||
return String(parsedUrl.href) as T;
|
return String(parsedUrl.href) as T;
|
||||||
}
|
} else {
|
||||||
else {
|
if (!stringUrl.startsWith("/")) {
|
||||||
if (!stringUrl.startsWith('/')) {
|
|
||||||
console.log("Don't know how to deal with relative path: ", url);
|
console.log("Don't know how to deal with relative path: ", url);
|
||||||
}
|
}
|
||||||
//console.log("Converted ", url, urlPrefix + stringUrl)
|
//console.log("Converted ", url, urlPrefix + stringUrl)
|
||||||
return (urlPrefix + stringUrl) as T;
|
return (urlPrefix + stringUrl) as T;
|
||||||
}
|
}
|
||||||
}
|
} else if (url.protocol === "http:" || url.protocol === "https:") {
|
||||||
else if (url.protocol === 'http:' || url.protocol === 'https:') {
|
|
||||||
const parsedUrl = new URL(url as URL);
|
const parsedUrl = new URL(url as URL);
|
||||||
parsedUrl.pathname = urlPrefix + parsedUrl.pathname;
|
parsedUrl.pathname = urlPrefix + parsedUrl.pathname;
|
||||||
//console.log("Converted ", url, parsedUrl)
|
//console.log("Converted ", url, parsedUrl)
|
||||||
return parsedUrl as T;
|
return parsedUrl as T;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toInternalUrl = (url: string) => {
|
export const toInternalUrl = (url: string) => {
|
||||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
if (url.startsWith("http://") || url.startsWith("https://")) {
|
||||||
const parsedUrl = new URL(url);
|
const parsedUrl = new URL(url);
|
||||||
if (parsedUrl.pathname.startsWith(urlPrefix)) {
|
if (parsedUrl.pathname.startsWith(urlPrefix)) {
|
||||||
parsedUrl.pathname = parsedUrl.pathname.substr(urlPrefix.length);
|
parsedUrl.pathname = parsedUrl.pathname.substr(urlPrefix.length);
|
||||||
return parsedUrl.href;
|
return parsedUrl.href;
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
console.log("Don't know how to deal with non-prefix: ", url);
|
console.log("Don't know how to deal with non-prefix: ", url);
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
} else if (url.startsWith(urlPrefix)) {
|
} else if (url.startsWith(urlPrefix)) {
|
||||||
return url.substr(urlPrefix.length);
|
return url.substr(urlPrefix.length);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
console.log("Don't know how to deal with non-prefix: ", url);
|
console.log("Don't know how to deal with non-prefix: ", url);
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -60,12 +60,13 @@ export class JWTIssuer {
|
||||||
|
|
||||||
createJWT(
|
createJWT(
|
||||||
payload: Record<string, unknown>,
|
payload: Record<string, unknown>,
|
||||||
expirySeconds: number,
|
expirySeconds?: number,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
return create({ alg: "HS512", typ: "JWT" }, {
|
const jwtPayload = { ...payload };
|
||||||
...payload,
|
if (expirySeconds) {
|
||||||
exp: getNumericDate(expirySeconds),
|
jwtPayload.exp = getNumericDate(expirySeconds);
|
||||||
}, this.key);
|
}
|
||||||
|
return create({ alg: "HS512", typ: "JWT" }, jwtPayload, this.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyAndDecodeJWT(jwt: string): Promise<Record<string, unknown>> {
|
verifyAndDecodeJWT(jwt: string): Promise<Record<string, unknown>> {
|
||||||
|
|
|
@ -20,7 +20,7 @@ import {
|
||||||
parsePageRef,
|
parsePageRef,
|
||||||
} from "@silverbulletmd/silverbullet/lib/page_ref";
|
} from "@silverbulletmd/silverbullet/lib/page_ref";
|
||||||
import { base64Encode } from "$lib/crypto.ts";
|
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
|
const authenticationExpirySeconds = 60 * 60 * 24 * 7; // 1 week
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ export class HttpServer {
|
||||||
spaceServer.pagesPath.replaceAll("\\", "\\\\"),
|
spaceServer.pagesPath.replaceAll("\\", "\\\\"),
|
||||||
).replaceAll(
|
).replaceAll(
|
||||||
"{{URL_PREFIX}}",
|
"{{URL_PREFIX}}",
|
||||||
urlPrefix
|
urlPrefix,
|
||||||
)
|
)
|
||||||
.replace(
|
.replace(
|
||||||
"{{DESCRIPTION}}",
|
"{{DESCRIPTION}}",
|
||||||
|
@ -323,6 +323,7 @@ export class HttpServer {
|
||||||
this.app.get("/.logout", (c) => {
|
this.app.get("/.logout", (c) => {
|
||||||
const url = new URL(toInternalUrl(c.req.url));
|
const url = new URL(toInternalUrl(c.req.url));
|
||||||
deleteCookie(c, authCookieName(url.host));
|
deleteCookie(c, authCookieName(url.host));
|
||||||
|
deleteCookie(c, "refreshLogin");
|
||||||
|
|
||||||
return c.redirect(toRealUrl("/.auth"));
|
return c.redirect(toRealUrl("/.auth"));
|
||||||
});
|
});
|
||||||
|
@ -331,7 +332,7 @@ export class HttpServer {
|
||||||
const html = this.clientAssetBundle.readTextFileSync(".client/auth.html")
|
const html = this.clientAssetBundle.readTextFileSync(".client/auth.html")
|
||||||
.replaceAll(
|
.replaceAll(
|
||||||
"{{URL_PREFIX}}",
|
"{{URL_PREFIX}}",
|
||||||
urlPrefix
|
urlPrefix,
|
||||||
);
|
);
|
||||||
|
|
||||||
return c.html(html);
|
return c.html(html);
|
||||||
|
@ -339,20 +340,22 @@ export class HttpServer {
|
||||||
validator("form", (value, c) => {
|
validator("form", (value, c) => {
|
||||||
const username = value["username"];
|
const username = value["username"];
|
||||||
const password = value["password"];
|
const password = value["password"];
|
||||||
|
const rememberMe = value["rememberMe"];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!username || typeof username !== "string" ||
|
!username || typeof username !== "string" ||
|
||||||
!password || typeof password !== "string"
|
!password || typeof password !== "string" ||
|
||||||
|
(rememberMe && typeof rememberMe !== "string")
|
||||||
) {
|
) {
|
||||||
return c.redirect(toRealUrl("/.auth?error=0"));
|
return c.redirect(toRealUrl("/.auth?error=0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
return { username, password };
|
return { username, password, rememberMe };
|
||||||
}),
|
}),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const req = c.req;
|
const req = c.req;
|
||||||
const url = new URL(toInternalUrl(c.req.url));
|
const url = new URL(c.req.url);
|
||||||
const { username, password } = req.valid("form");
|
const { username, password, rememberMe } = req.valid("form");
|
||||||
|
|
||||||
const {
|
const {
|
||||||
user: expectedUser,
|
user: expectedUser,
|
||||||
|
@ -361,18 +364,24 @@ export class HttpServer {
|
||||||
|
|
||||||
if (username === expectedUser && password === expectedPassword) {
|
if (username === expectedUser && password === expectedPassword) {
|
||||||
// Generate a JWT and set it as a cookie
|
// Generate a JWT and set it as a cookie
|
||||||
const jwt = await this.spaceServer.jwtIssuer.createJWT(
|
const jwt = rememberMe
|
||||||
|
? await this.spaceServer.jwtIssuer.createJWT({ username })
|
||||||
|
: await this.spaceServer.jwtIssuer.createJWT(
|
||||||
{ username },
|
{ username },
|
||||||
authenticationExpirySeconds,
|
authenticationExpirySeconds,
|
||||||
);
|
);
|
||||||
console.log("Successful auth");
|
console.log("Successful auth");
|
||||||
setCookie(c, authCookieName(url.host), jwt, {
|
const inAWeek = new Date(
|
||||||
expires: new Date(
|
|
||||||
Date.now() + authenticationExpirySeconds * 1000,
|
Date.now() + authenticationExpirySeconds * 1000,
|
||||||
), // in a week
|
);
|
||||||
|
setCookie(c, authCookieName(url.host), jwt, {
|
||||||
|
expires: inAWeek,
|
||||||
// sameSite: "Strict",
|
// sameSite: "Strict",
|
||||||
// httpOnly: true,
|
// httpOnly: true,
|
||||||
});
|
});
|
||||||
|
if (rememberMe) {
|
||||||
|
setCookie(c, "refreshLogin", "true", { expires: inAWeek });
|
||||||
|
}
|
||||||
const values = await c.req.parseBody();
|
const values = await c.req.parseBody();
|
||||||
const from = values["from"];
|
const from = values["from"];
|
||||||
const result = toRealUrl(typeof from === "string" ? from : "/");
|
const result = toRealUrl(typeof from === "string" ? from : "/");
|
||||||
|
@ -380,7 +389,7 @@ export class HttpServer {
|
||||||
return c.redirect(result);
|
return c.redirect(result);
|
||||||
} else {
|
} else {
|
||||||
console.error("Authentication failed, redirecting to auth page.");
|
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) => {
|
).all((c) => {
|
||||||
|
@ -414,6 +423,7 @@ export class HttpServer {
|
||||||
const authToken = authHeader.slice("Bearer ".length);
|
const authToken = authHeader.slice("Bearer ".length);
|
||||||
if (authToken === this.spaceServer.authToken) {
|
if (authToken === this.spaceServer.authToken) {
|
||||||
// All good, let's proceed
|
// All good, let's proceed
|
||||||
|
this.refreshLogin(c, host);
|
||||||
return next();
|
return next();
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
console.log(
|
||||||
|
@ -445,10 +455,28 @@ export class HttpServer {
|
||||||
return redirectToAuth();
|
return redirectToAuth();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.refreshLogin(c, host);
|
||||||
return next();
|
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() {
|
private addFsRoutes() {
|
||||||
this.app.use(
|
this.app.use(
|
||||||
"*",
|
"*",
|
||||||
|
|
|
@ -69,7 +69,10 @@
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<h1>
|
<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
|
SilverBullet
|
||||||
</h1>
|
</h1>
|
||||||
</header>
|
</header>
|
||||||
|
@ -95,6 +98,10 @@
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" name="rememberMe" id="rememberMe" />
|
||||||
|
<label>Remember me</label>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<input type="submit" value="Login" />
|
<input type="submit" value="Login" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,7 +7,7 @@ import type { Client } from "./client.ts";
|
||||||
import { cleanPageRef } from "@silverbulletmd/silverbullet/lib/resolve";
|
import { cleanPageRef } from "@silverbulletmd/silverbullet/lib/resolve";
|
||||||
import { renderTheTemplate } from "$common/syscalls/template.ts";
|
import { renderTheTemplate } from "$common/syscalls/template.ts";
|
||||||
import { safeRun } from "../lib/async.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 & {
|
export type PageState = PageRef & {
|
||||||
scrollTop?: number;
|
scrollTop?: number;
|
||||||
|
|
|
@ -16,7 +16,7 @@ import type { UploadFile } from "../../plug-api/types.ts";
|
||||||
import type { PageRef } from "@silverbulletmd/silverbullet/lib/page_ref";
|
import type { PageRef } from "@silverbulletmd/silverbullet/lib/page_ref";
|
||||||
import { openSearchPanel } from "@codemirror/search";
|
import { openSearchPanel } from "@codemirror/search";
|
||||||
import { diffAndPrepareChanges } from "../cm_util.ts";
|
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 {
|
export function editorSyscalls(client: Client): SysCallMapping {
|
||||||
const syscalls: SysCallMapping = {
|
const syscalls: SysCallMapping = {
|
||||||
|
|
Loading…
Reference in New Issue