Refactor to remove sign up and JWT env variables (#65)

closes #63
closes #66
This commit is contained in:
sua yoo 2021-12-06 19:39:04 -08:00 committed by GitHub
parent e787e751d9
commit 3324bd960f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 68 additions and 53 deletions

View File

@ -1,2 +1 @@
API_BASE_URL='https://btrix.webrecorder.net/api' API_BASE_URL=https://btrix.webrecorder.net/api
REGISTRATION_ENABLED=false

View File

@ -37,7 +37,6 @@ follow instructions for deploying to a local Docker instance. Update `API_BASE_U
| `format` | formats js, html and css files | | `format` | formats js, html and css files |
| `localize:extract` | generate XLIFF file to be translated | | `localize:extract` | generate XLIFF file to be translated |
| `localize:build` | output a localized version of strings/templates | | `localize:build` | output a localized version of strings/templates |
| `env:sync` | sync environment variables with API settings |
## Testing ## Testing

View File

@ -19,15 +19,14 @@
}, },
"scripts": { "scripts": {
"test": "web-test-runner \"src/**/*.test.{ts,js}\" --node-resolve --playwright --browsers chromium", "test": "web-test-runner \"src/**/*.test.{ts,js}\" --node-resolve --playwright --browsers chromium",
"prebuild": "del-cli ./dist && npm run env:sync", "prebuild": "del-cli ./dist",
"build": "webpack --config webpack.prod.js", "build": "webpack --config webpack.prod.js",
"build-dev": "webpack --mode development", "build-dev": "webpack --mode development",
"start": "webpack serve --mode=development", "start": "webpack serve --mode=development",
"lint": "eslint --fix \"src/**/*.{ts,js}\"", "lint": "eslint --fix \"src/**/*.{ts,js}\"",
"format": "prettier --write \"**/*.{ts,js,html,css}\"", "format": "prettier --write \"**/*.{ts,js,html,css}\"",
"localize:extract": "lit-localize extract", "localize:extract": "lit-localize extract",
"localize:build": "lit-localize build", "localize:build": "lit-localize build"
"env:sync": "node ./scripts/get-settings.mjs"
}, },
"devDependencies": { "devDependencies": {
"@esm-bundle/chai": "^4.3.4-fix.0", "@esm-bundle/chai": "^4.3.4-fix.0",

View File

@ -1,2 +1 @@
API_BASE_URL='http://btrix.cloud' API_BASE_URL=https://btrix-dev.webrecorder.net/api
REGISTRATION_ENABLED=true

View File

@ -1,28 +0,0 @@
import fetch from "node-fetch";
import updateDotenv from "update-dotenv";
import dotenv from "dotenv";
dotenv.config();
async function main() {
try {
const resp = await fetch(`${process.env.API_BASE_URL}/settings`);
const body = await resp.json();
const newEnv = await updateDotenv({
REGISTRATION_ENABLED: Boolean(body.enabled).toString(),
});
console.log(
".env file updated:",
`REGISTRATION_ENABLED=${newEnv["REGISTRATION_ENABLED"]}`
);
} catch {
console.log(
"could not update .env file, env is now:",
`REGISTRATION_ENABLED=${process.env.REGISTRATION_ENABLED}`
);
}
}
main();

View File

@ -19,8 +19,6 @@ import "./shoelace";
import "./components"; import "./components";
import "./pages"; import "./pages";
const REGISTRATION_ENABLED = process.env.REGISTRATION_ENABLED === "true";
type DialogContent = { type DialogContent = {
label?: TemplateResult | string; label?: TemplateResult | string;
body?: TemplateResult | string; body?: TemplateResult | string;
@ -56,6 +54,12 @@ export class App extends LiteElement {
@query("#globalDialog") @query("#globalDialog")
private globalDialog!: SlDialog; private globalDialog!: SlDialog;
@state()
private isAppSettingsLoaded: boolean = false;
@state()
private isRegistrationEnabled?: boolean;
constructor() { constructor() {
super(); super();
@ -93,10 +97,18 @@ export class App extends LiteElement {
}); });
} }
firstUpdated() { async firstUpdated() {
if (this.authService.authState) { if (this.authService.authState) {
this.updateUserInfo(); this.updateUserInfo();
} }
const settings = await this.getAppSettings();
if (settings) {
this.isRegistrationEnabled = settings.registrationEnabled;
}
this.isAppSettingsLoaded = true;
} }
private async updateUserInfo() { private async updateUserInfo() {
@ -118,6 +130,20 @@ export class App extends LiteElement {
} }
} }
async getAppSettings(): Promise<{ registrationEnabled: boolean } | void> {
const resp = await fetch("/api/settings", {
headers: { "Content-Type": "application/json" },
});
if (resp.status === 200) {
const body = await resp.json();
return body;
} else {
console.debug(resp);
}
}
navigate(newViewPath: string) { navigate(newViewPath: string) {
if (newViewPath.startsWith("http")) { if (newViewPath.startsWith("http")) {
const url = new URL(newViewPath); const url = new URL(newViewPath);
@ -261,7 +287,12 @@ export class App extends LiteElement {
switch (this.viewState.route) { switch (this.viewState.route) {
case "signUp": { case "signUp": {
if (REGISTRATION_ENABLED) { if (!this.isAppSettingsLoaded) {
return html`<div
class="w-full md:bg-gray-100 flex items-center justify-center"
></div>`;
}
if (this.isRegistrationEnabled) {
return html`<btrix-sign-up return html`<btrix-sign-up
class="w-full md:bg-gray-100 flex items-center justify-center" class="w-full md:bg-gray-100 flex items-center justify-center"
@navigate="${this.onNavigateTo}" @navigate="${this.onNavigateTo}"

View File

@ -5,6 +5,7 @@ export type Auth = {
headers: { headers: {
Authorization: string; Authorization: string;
}; };
/** Timestamp (milliseconds) when token expires */
tokenExpiresAt: number; tokenExpiresAt: number;
}; };
@ -14,6 +15,12 @@ type Session = {
export type AuthState = (Auth & Session) | null; export type AuthState = (Auth & Session) | null;
type JWT = {
user_id: string;
aud: string[];
exp: number;
};
export type LoggedInEventDetail = Auth & { export type LoggedInEventDetail = Auth & {
api?: boolean; api?: boolean;
firstLogin?: boolean; firstLogin?: boolean;
@ -26,9 +33,6 @@ export interface LoggedInEvent<T = LoggedInEventDetail> extends CustomEvent {
// Check for token freshness every 5 minutes // Check for token freshness every 5 minutes
const FRESHNESS_TIMER_INTERVAL = 60 * 1000 * 5; const FRESHNESS_TIMER_INTERVAL = 60 * 1000 * 5;
// TODO get expires at from server
// Hardcode 1hr expiry for now
const ACCESS_TOKEN_LIFETIME = 1000 * 60 * 60;
// Hardcode 24h expiry for now // Hardcode 24h expiry for now
const SESSION_LIFETIME = 1000 * 60 * 60 * 24; const SESSION_LIFETIME = 1000 * 60 * 60 * 24;
@ -74,17 +78,27 @@ export default class AuthService {
}); });
} }
const authHeaders = AuthService.parseAuthHeaders(await resp.json()); const data = await resp.json();
const token = AuthService.decodeToken(data.access_token);
const authHeaders = AuthService.parseAuthHeaders(data);
return { return {
username: email, username: email,
headers: authHeaders, headers: authHeaders,
// TODO get expires at from server tokenExpiresAt: token.exp * 1000,
// Hardcode 1hr expiry for now
tokenExpiresAt: Date.now() + ACCESS_TOKEN_LIFETIME,
}; };
} }
/**
* Decode JSON web token returned as access token
*/
private static decodeToken(token: string): JWT {
return JSON.parse(window.atob(token.split(".")[1]));
}
/**
* Build authorization headers from login response
*/
private static parseAuthHeaders(data: { private static parseAuthHeaders(data: {
token_type: string; token_type: string;
access_token: string; access_token: string;
@ -169,6 +183,7 @@ export default class AuthService {
} }
} }
} else { } else {
console.info("Session expired, logging out");
this.logout(); this.logout();
} }
} }
@ -193,13 +208,13 @@ export default class AuthService {
}); });
} }
const authHeaders = AuthService.parseAuthHeaders(await resp.json()); const data = await resp.json();
const token = AuthService.decodeToken(data.access_token);
const authHeaders = AuthService.parseAuthHeaders(data);
return { return {
headers: authHeaders, headers: authHeaders,
// TODO get expires at from server tokenExpiresAt: token.exp * 1000,
// Hardcode 1hr expiry for now
tokenExpiresAt: Date.now() + ACCESS_TOKEN_LIFETIME,
}; };
} }
} }

View File

@ -15,8 +15,9 @@ require("dotenv").config({
path: dotEnvPath, path: dotEnvPath,
}); });
// TODO actual prod URL const backendUrl = new URL(
const backendUrl = new URL(process.env.API_BASE_URL || "http://btrix.cloud/"); process.env.API_BASE_URL || "https://btrix.webrecorder.net/"
);
const shoelaceAssetsSrcPath = path.resolve( const shoelaceAssetsSrcPath = path.resolve(
__dirname, __dirname,
"node_modules/@shoelace-style/shoelace/dist/assets" "node_modules/@shoelace-style/shoelace/dist/assets"