Merge remote-tracking branch 'origin/main' into pr/1131 and deno task fmt
commit
ce5992719c
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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>> {
|
||||
|
|
|
@ -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(
|
||||
"*",
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -88,7 +88,7 @@ declare global {
|
|||
syncOnly: boolean;
|
||||
readOnly: boolean;
|
||||
enableSpaceScript: boolean;
|
||||
urlPrefix : string;
|
||||
urlPrefix: string;
|
||||
};
|
||||
// deno-lint-ignore no-var
|
||||
var client: Client;
|
||||
|
|
|
@ -109,4 +109,4 @@ function loadJsByUrl(url,integrity=null) {
|
|||
<body>
|
||||
|
||||
</body>
|
||||
</html>`;
|
||||
</html>`;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = {
|
||||
|
|
Loading…
Reference in New Issue