From dd10cae1cde0b777538536f56551a0680cec42fa Mon Sep 17 00:00:00 2001 From: Fabian Stamm Date: Tue, 3 Nov 2020 23:29:56 +0100 Subject: [PATCH] Improve popup window support. Switching to new views_repo with new build system --- src/api/oauth/refresh.ts | 3 +- src/api/user/index.ts | 4 +- src/api/user/jwt.ts | 37 -- src/api/user/oauth/_helper.ts | 21 + src/api/user/oauth/index.ts | 12 + src/api/user/oauth/jwt.ts | 25 + src/api/user/oauth/permissions.ts | 38 ++ src/api/user/oauth/refresh_token.ts | 49 ++ src/config.ts | 5 +- src/views/views.ts | 16 +- views/src/login/login.hbs | 56 -- views/src/login/login.js | 166 ------ views/src/login/login.scss | 132 ----- views/src/login/login.tsx | 434 -------------- views/src/login/u2f-api-polyfill.js | 844 ---------------------------- views/src/popup/popup.hbs | 24 - views/src/popup/popup.js | 32 -- views/src/popup/popup.scss | 63 --- views_repo | 2 +- 19 files changed, 163 insertions(+), 1800 deletions(-) delete mode 100644 src/api/user/jwt.ts create mode 100644 src/api/user/oauth/_helper.ts create mode 100644 src/api/user/oauth/index.ts create mode 100644 src/api/user/oauth/jwt.ts create mode 100644 src/api/user/oauth/permissions.ts create mode 100644 src/api/user/oauth/refresh_token.ts delete mode 100644 views/src/login/login.hbs delete mode 100644 views/src/login/login.js delete mode 100644 views/src/login/login.scss delete mode 100644 views/src/login/login.tsx delete mode 100644 views/src/login/u2f-api-polyfill.js delete mode 100644 views/src/popup/popup.hbs delete mode 100644 views/src/popup/popup.js delete mode 100644 views/src/popup/popup.scss diff --git a/src/api/oauth/refresh.ts b/src/api/oauth/refresh.ts index 4357b28..277d848 100644 --- a/src/api/oauth/refresh.ts +++ b/src/api/oauth/refresh.ts @@ -16,6 +16,7 @@ import moment = require("moment"); // import { JWTExpDur } from "../../keys"; import RefreshToken from "../../models/refresh_token"; import { getEncryptionKey } from "../../helper/user_key"; +import { refreshTokenValidTime } from "../../config"; // TODO: /* @@ -27,8 +28,6 @@ legitimate client, one of them will present an invalidated refresh token, which will inform the authorization server of the breach. */ -const refreshTokenValidTime = moment.duration(6, "month"); - const RefreshTokenRoute = Stacker( GetClientAuthMiddleware(false, false, true), async (req: Request, res: Response) => { diff --git a/src/api/user/index.ts b/src/api/user/index.ts index 4d54321..d565e8c 100644 --- a/src/api/user/index.ts +++ b/src/api/user/index.ts @@ -1,11 +1,11 @@ import { Router } from "express"; import { GetAccount } from "./account"; import { GetContactInfos } from "./contact"; -import { GetJWTByUser } from "./jwt"; import Login from "./login"; import Register from "./register"; import { DeleteToken, GetToken } from "./token"; import TwoFactorRoute from "./twofactor"; +import OAuthRoute from "./oauth"; const UserRoute: Router = Router(); @@ -127,6 +127,6 @@ UserRoute.get("/account", GetAccount); */ UserRoute.get("/contact", GetContactInfos); -UserRoute.get("/jwt", GetJWTByUser); +UserRoute.use("/oauth", OAuthRoute); export default UserRoute; diff --git a/src/api/user/jwt.ts b/src/api/user/jwt.ts deleted file mode 100644 index eb3bb82..0000000 --- a/src/api/user/jwt.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Request, Response } from "express"; -import Stacker from "../middlewares/stacker"; -import { GetUserMiddleware } from "../middlewares/user"; -import { URL } from "url"; -import Client from "../../models/client"; -import RequestError, { HttpStatusCode } from "../../helper/request_error"; -import { getAccessTokenJWT } from "../../helper/jwt"; - -export const GetJWTByUser = Stacker( - GetUserMiddleware(true, false), - async (req: Request, res: Response) => { - const { client_id, origin } = req.query as { [key: string]: string }; - - const client = await Client.findOne({ - client_id, - }); - - const clientNotFoundError = new RequestError( - "Client not found!", - HttpStatusCode.BAD_REQUEST - ); - - if (!client) throw clientNotFoundError; - - const clientUrl = new URL(client.redirect_url); - - if (clientUrl.hostname !== origin) throw clientNotFoundError; - - const jwt = await getAccessTokenJWT({ - user: req.user, - client: client, - permissions: [], - }); - - res.json({ jwt }); - } -); diff --git a/src/api/user/oauth/_helper.ts b/src/api/user/oauth/_helper.ts new file mode 100644 index 0000000..1fd6c84 --- /dev/null +++ b/src/api/user/oauth/_helper.ts @@ -0,0 +1,21 @@ +import RequestError, { HttpStatusCode } from "../../../helper/request_error"; +import Client, { IClient } from "../../../models/client"; + +export async function getClientWithOrigin(client_id: string, origin: string) { + const client = await Client.findOne({ + client_id, + }); + + const clientNotFoundError = new RequestError( + "Client not found!", + HttpStatusCode.BAD_REQUEST + ); + + if (!client) throw clientNotFoundError; + + const clientUrl = new URL(client.redirect_url); + + if (clientUrl.hostname !== origin) throw clientNotFoundError; + + return client; +} diff --git a/src/api/user/oauth/index.ts b/src/api/user/oauth/index.ts new file mode 100644 index 0000000..cab9ada --- /dev/null +++ b/src/api/user/oauth/index.ts @@ -0,0 +1,12 @@ +import { Router } from "express"; +import { GetJWTByUser } from "./jwt"; +import { GetPermissionsForAuthRequest } from "./permissions"; +import { GetTokenByUser } from "./refresh_token"; + +const router = Router(); + +router.get("/jwt", GetJWTByUser); +router.get("/permissions", GetPermissionsForAuthRequest); +router.get("/refresh_token", GetTokenByUser); + +export default router; diff --git a/src/api/user/oauth/jwt.ts b/src/api/user/oauth/jwt.ts new file mode 100644 index 0000000..beca8b9 --- /dev/null +++ b/src/api/user/oauth/jwt.ts @@ -0,0 +1,25 @@ +import { Request, Response } from "express"; +import Stacker from "../../middlewares/stacker"; +import { GetUserMiddleware } from "../../middlewares/user"; +import { URL } from "url"; +import Client from "../../../models/client"; +import RequestError, { HttpStatusCode } from "../../../helper/request_error"; +import { getAccessTokenJWT } from "../../../helper/jwt"; +import { getClientWithOrigin } from "./_helper"; + +export const GetJWTByUser = Stacker( + GetUserMiddleware(true, false), + async (req: Request, res: Response) => { + const { client_id, origin } = req.query as { [key: string]: string }; + + const client = await getClientWithOrigin(client_id, origin); + + const jwt = await getAccessTokenJWT({ + user: req.user, + client: client, + permissions: [], + }); + + res.json({ jwt }); + } +); diff --git a/src/api/user/oauth/permissions.ts b/src/api/user/oauth/permissions.ts new file mode 100644 index 0000000..395c4df --- /dev/null +++ b/src/api/user/oauth/permissions.ts @@ -0,0 +1,38 @@ +import { Request, Response } from "express"; +import Stacker from "../../middlewares/stacker"; +import { GetUserMiddleware } from "../../middlewares/user"; +import { URL } from "url"; +import Client from "../../../models/client"; +import RequestError, { HttpStatusCode } from "../../../helper/request_error"; +import { randomBytes } from "crypto"; +import moment = require("moment"); +import RefreshToken from "../../../models/refresh_token"; +import { refreshTokenValidTime } from "../../../config"; +import { getClientWithOrigin } from "./_helper"; +import Permission from "../../../models/permissions"; + +export const GetPermissionsForAuthRequest = Stacker( + GetUserMiddleware(true, false), + async (req: Request, res: Response) => { + const { client_id, origin, permissions } = req.query as { + [key: string]: string; + }; + + const client = await getClientWithOrigin(client_id, origin); + + const perm = permissions.split(",").filter((e) => !!e); + + const resolved = await Promise.all( + perm.map((p) => Permission.findById(p)) + ); + + if (resolved.some((e) => e.grant_type !== "user")) { + throw new RequestError( + "Invalid Permission requested", + HttpStatusCode.BAD_REQUEST + ); + } + + res.json({ permissions: resolved }); + } +); diff --git a/src/api/user/oauth/refresh_token.ts b/src/api/user/oauth/refresh_token.ts new file mode 100644 index 0000000..4ff4cc4 --- /dev/null +++ b/src/api/user/oauth/refresh_token.ts @@ -0,0 +1,49 @@ +import { Request, Response } from "express"; +import Stacker from "../../middlewares/stacker"; +import { GetUserMiddleware } from "../../middlewares/user"; +import { URL } from "url"; +import Client from "../../../models/client"; +import RequestError, { HttpStatusCode } from "../../../helper/request_error"; +import { randomBytes } from "crypto"; +import moment = require("moment"); +import RefreshToken from "../../../models/refresh_token"; +import { refreshTokenValidTime } from "../../../config"; +import { getClientWithOrigin } from "./_helper"; +import Permission from "../../../models/permissions"; + +export const GetTokenByUser = Stacker( + GetUserMiddleware(true, false), + async (req: Request, res: Response) => { + const { client_id, origin, permissions } = req.query as { + [key: string]: string; + }; + + const client = await getClientWithOrigin(client_id, origin); + + const perm = permissions.split(",").filter((e) => !!e); + + const resolved = await Promise.all( + perm.map((p) => Permission.findById(p)) + ); + + if (resolved.some((e) => e.grant_type !== "user")) { + throw new RequestError( + "Invalid Permission requested", + HttpStatusCode.BAD_REQUEST + ); + } + + let token = RefreshToken.new({ + user: req.user._id, + client: client._id, + permissions: resolved.map((e) => e._id), + token: randomBytes(16).toString("hex"), + valid: true, + validTill: moment().add(refreshTokenValidTime).toDate(), + }); + + await RefreshToken.save(token); + + res.json({ token }); + } +); diff --git a/src/config.ts b/src/config.ts index fa5f1f0..956742c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,8 +1,9 @@ import { parse } from "@hibas123/config"; import { Logging } from "@hibas123/nodelogging"; import * as dotenv from "dotenv"; -import { readFileSync } from "fs"; -import * as ini from "ini"; +import moment = require("moment"); + +export const refreshTokenValidTime = moment.duration(6, "month"); dotenv.config(); diff --git a/src/views/views.ts b/src/views/views.ts index f8acc67..6f18d3d 100644 --- a/src/views/views.ts +++ b/src/views/views.ts @@ -40,13 +40,13 @@ ViewRouter.get("/register", (req, res) => { ViewRouter.use( "/login", addCache, - ServeStatic("./views_repo/build/login", { cacheControl: false }) + ServeStatic("./views_repo/build/Login", { cacheControl: false }) ); ViewRouter.use( "/user", addCache, - ServeStatic("./views_repo/build/user", { cacheControl: false }) + ServeStatic("./views_repo/build/User", { cacheControl: false }) ); ViewRouter.get("/code", (req, res) => { @@ -69,9 +69,15 @@ ViewRouter.get( ViewRouter.get("/auth", GetAuthRoute(true)); -ViewRouter.get("/popup", UserMiddleware, (req, res) => { - res.send(GetPopupPage(req.__)); -}); +ViewRouter.use( + "/popup", + addCache, + ServeStatic("./views_repo/build/Popup", { cacheControl: false }) +); + +// ViewRouter.get("/popup", UserMiddleware, (req, res) => { +// res.send(GetPopupPage(req.__)); +// }); // if (config.core.dev) { // const logo = diff --git a/views/src/login/login.hbs b/views/src/login/login.hbs deleted file mode 100644 index 2988f8c..0000000 --- a/views/src/login/login.hbs +++ /dev/null @@ -1,56 +0,0 @@ - - - - {{i18n "Login"}} - - - - - -
- {{!--
-

{{i18n "Login"}}

-
-
-
-
-
-
-
-
- - - - - -
- -
-
- - -
-
-
    -
- -
- -
-
-
-
- --}} - - - \ No newline at end of file diff --git a/views/src/login/login.js b/views/src/login/login.js deleted file mode 100644 index 89613d0..0000000 --- a/views/src/login/login.js +++ /dev/null @@ -1,166 +0,0 @@ -import sha from "sha512"; -import { setCookie, getCookie } from "cookie"; -import "inputs"; - -const loader = document.getElementById("loader"); -const container = document.getElementById("container"); -const usernameinput = document.getElementById("username"); -const usernamegroup = document.getElementById("usernamegroup"); -const uerrorfield = document.getElementById("uerrorfield"); -const passwordinput = document.getElementById("password"); -const passwordgroup = document.getElementById("passwordgroup"); -const perrorfield = document.getElementById("perrorfield"); -const nextbutton = document.getElementById("nextbutton"); -const loginbutton = document.getElementById("loginbutton"); - -let username; -let salt; - -usernameinput.focus(); - -const loading = () => { - container.style.filter = "blur(2px)"; - loader.style.display = ""; -}; - -const loading_fin = () => { - container.style.filter = ""; - loader.style.display = "none"; -}; -loading_fin(); - -usernameinput.onkeydown = (e) => { - var keycode = e.keyCode ? e.keyCode : e.which; - if (keycode === 13) nextbutton.click(); - clearError(uerrorfield); -}; - -nextbutton.onclick = async () => { - loading(); - username = usernameinput.value; - try { - let res = await fetch( - "/api/user/login?type=username&username=" + username, - { - method: "POST", - } - ) - .then((e) => { - if (e.status !== 200) throw new Error(e.statusText); - return e.json(); - }) - .then((data) => { - if (data.error) { - return Promise.reject(new Error(data.error)); - } - return data; - }); - salt = res.salt; - usernamegroup.classList.add("invisible"); - passwordgroup.classList.remove("invisible"); - passwordinput.focus(); - } catch (e) { - showError(uerrorfield, e.message); - } - loading_fin(); -}; - -passwordinput.onkeydown = (e) => { - var keycode = e.keyCode ? e.keyCode : e.which; - if (keycode === 13) loginbutton.click(); - clearError(perrorfield); -}; - -loginbutton.onclick = async () => { - loading(); - let pw = sha(salt + passwordinput.value); - try { - let { login, special, tfa } = await fetch( - "/api/user/login?type=password", - { - method: "POST", - body: JSON.stringify({ - username: usernameinput.value, - password: pw, - }), - headers: { - "content-type": "application/json", - }, - } - ) - .then((e) => { - if (e.status !== 200) throw new Error(e.statusText); - return e.json(); - }) - .then((data) => { - if (data.error) { - return Promise.reject(new Error(data.error)); - } - return data; - }); - - setCookie("login", login.token, new Date(login.expires).toUTCString()); - setCookie( - "special", - special.token, - new Date(special.expires).toUTCString() - ); - let d = new Date(); - d.setTime(d.getTime() + 30 * 24 * 60 * 60 * 1000); //Keep the username 30 days - setCookie("username", username, d.toUTCString()); - let url = new URL(window.location.href); - let state = url.searchParams.get("state"); - let red = "/"; - - if (tfa) twofactor(tfa); - else { - if (state) { - let base64 = url.searchParams.get("base64"); - if (base64) red = atob(state); - else red = state; - } - window.location.href = red; - } - } catch (e) { - passwordinput.value = ""; - showError(perrorfield, e.message); - } - loading_fin(); -}; - -function clearError(field) { - field.innerText = ""; - field.classList.add("invisible"); -} - -function showError(field, error) { - field.innerText = error; - field.classList.remove("invisible"); -} - -username = getCookie("username"); -if (username) { - usernameinput.value = username; - - var evt = document.createEvent("HTMLEvents"); - evt.initEvent("change", false, true); - usernameinput.dispatchEvent(evt); -} - -function twofactor(tfa) { - let list = tfa - .map((entry) => { - switch (entry) { - case 0: // OTC - return "Authenticator App"; - case 1: // BACKUP - return "Backup Key"; - } - return undefined; - }) - .filter((e) => e !== undefined) - .reduce((p, c) => p + `
  • ${c}
  • `, ""); - - let tfl = document.getElementById("tflist"); - tfl.innerHTML = list; -} diff --git a/views/src/login/login.scss b/views/src/login/login.scss deleted file mode 100644 index f7f6ee8..0000000 --- a/views/src/login/login.scss +++ /dev/null @@ -1,132 +0,0 @@ -@import "@material/button/mdc-button"; -@import "inputs"; -@import "style"; - -.spanned-btn { - width: 100%; - background: $primary !important; // text-shadow: 1px 1px 0 rgba(39, 110, 204, .5); -} - -* { - box-sizing: border-box; -} - -body { - font-family: Helvetica; - background: #eee; - -webkit-font-smoothing: antialiased; -} - -header { - text-align: center; - margin-top: 4em; -} - -h1, -h3 { - font-weight: 300; -} - -h1 { - color: #636363; -} - -h3 { - color: $primary; -} - -form { - max-width: 380px; - margin: 4em auto; - padding: 3em 2em 2em 2em; - background: #fafafa; - border: 1px solid #ebebeb; - box-shadow: rgba(0, 0, 0, 0.14902) 0px 1px 1px 0px, - rgba(0, 0, 0, 0.09804) 0px 1px 2px 0px; - position: relative; -} - -.loader_box { - width: 64px; - height: 64px; - margin: auto; - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; -} - -.loader { - display: inline-block; - position: relative; - z-index: 100; -} -.loader:after { - content: " "; - display: block; - width: 46px; - height: 46px; - margin: 1px; - border-radius: 50%; - border: 5px solid #000000; - border-color: #000000 transparent #000000 transparent; - animation: loader 1.2s linear infinite; -} -@keyframes loader { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} - -footer { - text-align: center; -} - -footer p { - color: #888; - font-size: 13px; - letter-spacing: 0.4px; -} - -footer a { - color: $primary; - text-decoration: none; - transition: all 0.2s ease; -} - -footer a:hover { - color: #666; - text-decoration: underline; -} - -footer img { - width: 80px; - transition: all 0.2s ease; -} - -footer img:hover { - opacity: 0.83; -} - -footer img:focus, -footer a:focus { - outline: none; -} - -.invisible { - display: none; -} - -.errorColor { - background: $error !important; -} - -.error { - color: $error; - margin-top: 5px; - font-size: 13px; -} diff --git a/views/src/login/login.tsx b/views/src/login/login.tsx deleted file mode 100644 index 76fde9d..0000000 --- a/views/src/login/login.tsx +++ /dev/null @@ -1,434 +0,0 @@ -import { h, Component, render } from "preact"; -import "inputs"; -import "./u2f-api-polyfill"; - -import sha from "sha512"; -import { setCookie, getCookie } from "cookie"; - -let appname = "test"; - -function Loader() { - return ( -
    -
    -
    - ); -} - -class Username extends Component< - { username: string; onNext: (username: string, salt: string) => void }, - { error: string; loading: boolean } -> { - username_input: HTMLInputElement; - constructor() { - super(); - this.state = { error: undefined, loading: false }; - } - - async onClick() { - this.setState({ loading: true }); - try { - let res = await fetch( - "/api/user/login?type=username&username=" + - this.username_input.value, - { - method: "POST", - } - ) - .then((e) => { - if (e.status !== 200) throw new Error(e.statusText); - return e.json(); - }) - .then((data) => { - if (data.error) { - return Promise.reject(new Error(data.error)); - } - return data; - }); - let salt = res.salt; - this.props.onNext(this.username_input.value, salt); - } catch (err) { - this.setState({ - error: err.message, - }); - } - this.setState({ loading: false }); - } - - render() { - if (this.state.loading) return ; - return ( -
    -
    - { - let k = e.keyCode | e.which; - if (k === 13) this.onClick(); - this.setState({ error: undefined }); - }} - type="text" - value={ - this.username_input - ? this.username_input.value - : this.props.username - } - autofocus - ref={(elm) => (elm ? (this.username_input = elm) : undefined)} - /> - - - - {this.state.error ? ( -
    {this.state.error}
    - ) : undefined} -
    - -
    - ); - } -} - -enum TFATypes { - OTC, - BACKUP_CODE, - YUBI_KEY, - APP_ALLOW, -} - -interface TwoFactors { - id: string; - name: string; - type: TFATypes; -} - -class Password extends Component< - { - username: string; - salt: string; - onNext: (login: Token, special: Token, tfa: TwoFactors[]) => void; - }, - { error: string; loading: boolean } -> { - password_input: HTMLInputElement; - constructor() { - super(); - this.state = { error: undefined, loading: false }; - } - - async onClick() { - this.setState({ - loading: true, - }); - - try { - let pw = sha(this.props.salt + this.password_input.value); - let { login, special, tfa } = await fetch( - "/api/user/login?type=password", - { - method: "POST", - body: JSON.stringify({ - username: this.props.username, - password: pw, - }), - headers: { - "content-type": "application/json", - }, - } - ) - .then((e) => { - if (e.status !== 200) throw new Error(e.statusText); - return e.json(); - }) - .then((data) => { - if (data.error) { - return Promise.reject(new Error(data.error)); - } - return data; - }); - this.props.onNext(login, special, tfa); - } catch (err) { - this.setState({ error: err.messagae }); - } - this.setState({ loading: false }); - } - - render() { - if (this.state.loading) return ; - return ( -
    -
    - { - let k = e.keyCode | e.which; - if (k === 13) this.onClick(); - this.setState({ error: undefined }); - }} - type="password" - ref={(elm: HTMLInputElement) => { - if (elm) { - this.password_input = elm; - setTimeout(() => elm.focus(), 200); - // elm.focus(); - } - }} - /> - - - - {this.state.error ? ( -
    {this.state.error}
    - ) : undefined} -
    - -
    - ); - } -} - -class TwoFactor extends Component< - { twofactors: TwoFactors[]; next: (id: string, type: TFATypes) => void }, - {} -> { - render() { - let tfs = this.props.twofactors.map((fac) => { - let name: string; - switch (fac.type) { - case TFATypes.OTC: - name = "Authenticator"; - break; - case TFATypes.BACKUP_CODE: - name = "Backup code"; - break; - case TFATypes.APP_ALLOW: - name = "Use App: %s"; - break; - case TFATypes.YUBI_KEY: - name = "Use Yubikey: %s"; - break; - } - - name = name.replace("%s", fac.name ? fac.name : ""); - - return ( -
  • { - console.log("Click on Solution"); - this.props.next(fac.id, fac.type); - }} - > - {name} -
  • - ); - }); - return ( -
    -

    Select one

    -
      {tfs}
    -
    - ); - } -} - -// class TFA_YubiKey extends Component<{ id: string, login: Token, special: Token, next: (login: Token, special: Token) => void }, {}> { -// render() { - -// } -// } - -enum Page { - username, - password, - twofactor, - yubikey, -} - -interface Token { - token: string; - expires: string; -} - -async function apiRequest( - endpoint: string, - method: "GET" | "POST" | "DELETE" | "PUT" = "GET", - body: string = undefined -) { - return fetch(endpoint, { - method, - body, - credentials: "same-origin", - headers: { - "content-type": "application/json", - }, - }) - .then((e) => { - if (e.status !== 200) throw new Error(e.statusText); - return e.json(); - }) - .then((data) => { - if (data.error) { - return Promise.reject(new Error(data.error)); - } - return data; - }); -} - -class App extends Component< - {}, - { - page: Page; - username: string; - salt: string; - twofactor: TwoFactors[]; - twofactor_id: string; - } -> { - login: Token; - special: Token; - constructor() { - super(); - this.state = { - page: Page.username, - username: getCookie("username"), - salt: undefined, - twofactor: [], - twofactor_id: null, - }; - } - - setCookies() { - setCookie( - "login", - this.login.token, - new Date(this.login.expires).toUTCString() - ); - setCookie( - "special", - this.special.token, - new Date(this.special.expires).toUTCString() - ); - } - - finish() { - this.setCookies(); - let d = new Date(); - d.setTime(d.getTime() + 30 * 24 * 60 * 60 * 1000); //Keep the username 30 days - setCookie("username", this.state.username, d.toUTCString()); - let url = new URL(window.location.href); - let state = url.searchParams.get("state"); - let red = "/"; - - if (state) { - let base64 = url.searchParams.get("base64"); - if (base64) red = atob(state); - else red = state; - } - window.location.href = red; - } - - render() { - let cont; - switch (this.state.page) { - case Page.username: - cont = ( - { - this.setState({ username, salt, page: Page.password }); - localStorage.setItem("username", username); - }} - /> - ); - break; - case Page.password: - cont = ( - { - this.login = login; - this.special = special; - this.setCookies(); - - if (!twofactor) { - this.finish(); - } else { - this.setState({ twofactor, page: Page.twofactor }); - } - }} - /> - ); - break; - case Page.twofactor: - cont = ( - { - if (type === TFATypes.YUBI_KEY) { - let { request } = await apiRequest( - "/api/user/twofactor/yubikey", - "GET" - ); - console.log(request); - (window as any).u2f.sign( - request.appId, - [request.challenge], - [request], - async (response) => { - let res = await apiRequest( - "/api/user/twofactor/yubikey", - "PUT", - JSON.stringify({ response }) - ); - if (res.success) { - this.login.expires = res.login_exp; - this.special.expires = res.special_exp; - this.finish(); - } - } - ); - } - }} - /> - ); - break; - // case Page.yubikey: - // cont = { - // this.login = login; - // this.special = special; - // this.finish() - // }} /> - // break; - } - return ( -
    -
    -

    Login

    -
    -
    {cont}
    -
    -

    Powered by {appname}

    -
    -
    - ); - } -} - -document.addEventListener( - "DOMContentLoaded", - function () { - render(, document.body.querySelector("#content")); - }, - false -); diff --git a/views/src/login/u2f-api-polyfill.js b/views/src/login/u2f-api-polyfill.js deleted file mode 100644 index cb372bd..0000000 --- a/views/src/login/u2f-api-polyfill.js +++ /dev/null @@ -1,844 +0,0 @@ -//Copyright 2014-2015 Google Inc. All rights reserved. - -//Use of this source code is governed by a BSD-style -//license that can be found in the LICENSE file or at -//https://developers.google.com/open-source/licenses/bsd - -// NOTE FROM MAINTAINER: This file is copied from google/u2f-ref-code with as -// few alterations as possible. Any changes that were necessary are annotated -// with "NECESSARY CHANGE". These changes, as well as this note, should be -// preserved when updating this file from the source. - -/** - * @fileoverview The U2F api. - */ -"use strict"; - -// NECESSARY CHANGE: wrap the whole file in a closure -(function () { - // NECESSARY CHANGE: detect UA to avoid clobbering other browser's U2F API. - var isChrome = - "chrome" in window && window.navigator.userAgent.indexOf("Edge") < 0; - if ("u2f" in window || !isChrome) { - return; - } - - /** - * Namespace for the U2F api. - * @type {Object} - */ - // NECESSARY CHANGE: define the window.u2f API. - var u2f = (window.u2f = {}); - - /** - * FIDO U2F Javascript API Version - * @number - */ - var js_api_version; - - /** - * The U2F extension id - * @const {string} - */ - // The Chrome packaged app extension ID. - // Uncomment this if you want to deploy a server instance that uses - // the package Chrome app and does not require installing the U2F Chrome extension. - u2f.EXTENSION_ID = "kmendfapggjehodndflmmgagdbamhnfd"; - // The U2F Chrome extension ID. - // Uncomment this if you want to deploy a server instance that uses - // the U2F Chrome extension to authenticate. - // u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne'; - - /** - * Message types for messsages to/from the extension - * @const - * @enum {string} - */ - u2f.MessageTypes = { - U2F_REGISTER_REQUEST: "u2f_register_request", - U2F_REGISTER_RESPONSE: "u2f_register_response", - U2F_SIGN_REQUEST: "u2f_sign_request", - U2F_SIGN_RESPONSE: "u2f_sign_response", - U2F_GET_API_VERSION_REQUEST: "u2f_get_api_version_request", - U2F_GET_API_VERSION_RESPONSE: "u2f_get_api_version_response", - }; - - /** - * Response status codes - * @const - * @enum {number} - */ - u2f.ErrorCodes = { - OK: 0, - OTHER_ERROR: 1, - BAD_REQUEST: 2, - CONFIGURATION_UNSUPPORTED: 3, - DEVICE_INELIGIBLE: 4, - TIMEOUT: 5, - }; - - /** - * A message for registration requests - * @typedef {{ - * type: u2f.MessageTypes, - * appId: ?string, - * timeoutSeconds: ?number, - * requestId: ?number - * }} - */ - u2f.U2fRequest; - - /** - * A message for registration responses - * @typedef {{ - * type: u2f.MessageTypes, - * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse), - * requestId: ?number - * }} - */ - u2f.U2fResponse; - - /** - * An error object for responses - * @typedef {{ - * errorCode: u2f.ErrorCodes, - * errorMessage: ?string - * }} - */ - u2f.Error; - - /** - * Data object for a single sign request. - * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC, USB_INTERNAL}} - */ - u2f.Transport; - - /** - * Data object for a single sign request. - * @typedef {Array} - */ - u2f.Transports; - - /** - * Data object for a single sign request. - * @typedef {{ - * version: string, - * challenge: string, - * keyHandle: string, - * appId: string - * }} - */ - u2f.SignRequest; - - /** - * Data object for a sign response. - * @typedef {{ - * keyHandle: string, - * signatureData: string, - * clientData: string - * }} - */ - u2f.SignResponse; - - /** - * Data object for a registration request. - * @typedef {{ - * version: string, - * challenge: string - * }} - */ - u2f.RegisterRequest; - - /** - * Data object for a registration response. - * @typedef {{ - * version: string, - * keyHandle: string, - * transports: Transports, - * appId: string - * }} - */ - u2f.RegisterResponse; - - /** - * Data object for a registered key. - * @typedef {{ - * version: string, - * keyHandle: string, - * transports: ?Transports, - * appId: ?string - * }} - */ - u2f.RegisteredKey; - - /** - * Data object for a get API register response. - * @typedef {{ - * js_api_version: number - * }} - */ - u2f.GetJsApiVersionResponse; - - //Low level MessagePort API support - - /** - * Sets up a MessagePort to the U2F extension using the - * available mechanisms. - * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback - */ - u2f.getMessagePort = function (callback) { - if (typeof chrome != "undefined" && chrome.runtime) { - // The actual message here does not matter, but we need to get a reply - // for the callback to run. Thus, send an empty signature request - // in order to get a failure response. - var msg = { - type: u2f.MessageTypes.U2F_SIGN_REQUEST, - signRequests: [], - }; - chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function () { - if (!chrome.runtime.lastError) { - // We are on a whitelisted origin and can talk directly - // with the extension. - u2f.getChromeRuntimePort_(callback); - } else { - // chrome.runtime was available, but we couldn't message - // the extension directly, use iframe - u2f.getIframePort_(callback); - } - }); - } else if (u2f.isAndroidChrome_()) { - u2f.getAuthenticatorPort_(callback); - } else if (u2f.isIosChrome_()) { - u2f.getIosPort_(callback); - } else { - // chrome.runtime was not available at all, which is normal - // when this origin doesn't have access to any extensions. - u2f.getIframePort_(callback); - } - }; - - /** - * Detect chrome running on android based on the browser's useragent. - * @private - */ - u2f.isAndroidChrome_ = function () { - var userAgent = navigator.userAgent; - return ( - userAgent.indexOf("Chrome") != -1 && userAgent.indexOf("Android") != -1 - ); - }; - - /** - * Detect chrome running on iOS based on the browser's platform. - * @private - */ - u2f.isIosChrome_ = function () { - return ["iPhone", "iPad", "iPod"].indexOf(navigator.platform) > -1; - }; - - /** - * Connects directly to the extension via chrome.runtime.connect. - * @param {function(u2f.WrappedChromeRuntimePort_)} callback - * @private - */ - u2f.getChromeRuntimePort_ = function (callback) { - var port = chrome.runtime.connect(u2f.EXTENSION_ID, { - includeTlsChannelId: true, - }); - setTimeout(function () { - callback(new u2f.WrappedChromeRuntimePort_(port)); - }, 0); - }; - - /** - * Return a 'port' abstraction to the Authenticator app. - * @param {function(u2f.WrappedAuthenticatorPort_)} callback - * @private - */ - u2f.getAuthenticatorPort_ = function (callback) { - setTimeout(function () { - callback(new u2f.WrappedAuthenticatorPort_()); - }, 0); - }; - - /** - * Return a 'port' abstraction to the iOS client app. - * @param {function(u2f.WrappedIosPort_)} callback - * @private - */ - u2f.getIosPort_ = function (callback) { - setTimeout(function () { - callback(new u2f.WrappedIosPort_()); - }, 0); - }; - - /** - * A wrapper for chrome.runtime.Port that is compatible with MessagePort. - * @param {Port} port - * @constructor - * @private - */ - u2f.WrappedChromeRuntimePort_ = function (port) { - this.port_ = port; - }; - - /** - * Format and return a sign request compliant with the JS API version supported by the extension. - * @param {Array} signRequests - * @param {number} timeoutSeconds - * @param {number} reqId - * @return {Object} - */ - u2f.formatSignRequest_ = function ( - appId, - challenge, - registeredKeys, - timeoutSeconds, - reqId - ) { - if (js_api_version === undefined || js_api_version < 1.1) { - // Adapt request to the 1.0 JS API - var signRequests = []; - for (var i = 0; i < registeredKeys.length; i++) { - signRequests[i] = { - version: registeredKeys[i].version, - challenge: challenge, - keyHandle: registeredKeys[i].keyHandle, - appId: appId, - }; - } - return { - type: u2f.MessageTypes.U2F_SIGN_REQUEST, - signRequests: signRequests, - timeoutSeconds: timeoutSeconds, - requestId: reqId, - }; - } - // JS 1.1 API - return { - type: u2f.MessageTypes.U2F_SIGN_REQUEST, - appId: appId, - challenge: challenge, - registeredKeys: registeredKeys, - timeoutSeconds: timeoutSeconds, - requestId: reqId, - }; - }; - - /** - * Format and return a register request compliant with the JS API version supported by the extension.. - * @param {Array} signRequests - * @param {Array} signRequests - * @param {number} timeoutSeconds - * @param {number} reqId - * @return {Object} - */ - u2f.formatRegisterRequest_ = function ( - appId, - registeredKeys, - registerRequests, - timeoutSeconds, - reqId - ) { - if (js_api_version === undefined || js_api_version < 1.1) { - // Adapt request to the 1.0 JS API - for (var i = 0; i < registerRequests.length; i++) { - registerRequests[i].appId = appId; - } - var signRequests = []; - for (var i = 0; i < registeredKeys.length; i++) { - signRequests[i] = { - version: registeredKeys[i].version, - challenge: registerRequests[0], - keyHandle: registeredKeys[i].keyHandle, - appId: appId, - }; - } - return { - type: u2f.MessageTypes.U2F_REGISTER_REQUEST, - signRequests: signRequests, - registerRequests: registerRequests, - timeoutSeconds: timeoutSeconds, - requestId: reqId, - }; - } - // JS 1.1 API - return { - type: u2f.MessageTypes.U2F_REGISTER_REQUEST, - appId: appId, - registerRequests: registerRequests, - registeredKeys: registeredKeys, - timeoutSeconds: timeoutSeconds, - requestId: reqId, - }; - }; - - /** - * Posts a message on the underlying channel. - * @param {Object} message - */ - u2f.WrappedChromeRuntimePort_.prototype.postMessage = function (message) { - this.port_.postMessage(message); - }; - - /** - * Emulates the HTML 5 addEventListener interface. Works only for the - * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage. - * @param {string} eventName - * @param {function({data: Object})} handler - */ - u2f.WrappedChromeRuntimePort_.prototype.addEventListener = function ( - eventName, - handler - ) { - var name = eventName.toLowerCase(); - if (name == "message" || name == "onmessage") { - this.port_.onMessage.addListener(function (message) { - // Emulate a minimal MessageEvent object - handler({ data: message }); - }); - } else { - console.error("WrappedChromeRuntimePort only supports onMessage"); - } - }; - - /** - * Wrap the Authenticator app with a MessagePort interface. - * @constructor - * @private - */ - u2f.WrappedAuthenticatorPort_ = function () { - this.requestId_ = -1; - this.requestObject_ = null; - }; - - /** - * Launch the Authenticator intent. - * @param {Object} message - */ - u2f.WrappedAuthenticatorPort_.prototype.postMessage = function (message) { - var intentUrl = - u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ + - ";S.request=" + - encodeURIComponent(JSON.stringify(message)) + - ";end"; - document.location = intentUrl; - }; - - /** - * Tells what type of port this is. - * @return {String} port type - */ - u2f.WrappedAuthenticatorPort_.prototype.getPortType = function () { - return "WrappedAuthenticatorPort_"; - }; - - /** - * Emulates the HTML 5 addEventListener interface. - * @param {string} eventName - * @param {function({data: Object})} handler - */ - u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function ( - eventName, - handler - ) { - var name = eventName.toLowerCase(); - if (name == "message") { - var self = this; - /* Register a callback to that executes when - * chrome injects the response. */ - window.addEventListener( - "message", - self.onRequestUpdate_.bind(self, handler), - false - ); - } else { - console.error("WrappedAuthenticatorPort only supports message"); - } - }; - - /** - * Callback invoked when a response is received from the Authenticator. - * @param function({data: Object}) callback - * @param {Object} message message Object - */ - u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ = function ( - callback, - message - ) { - var messageObject = JSON.parse(message.data); - var intentUrl = messageObject["intentURL"]; - - var errorCode = messageObject["errorCode"]; - var responseObject = null; - if (messageObject.hasOwnProperty("data")) { - responseObject = /** @type {Object} */ (JSON.parse( - messageObject["data"] - )); - } - - callback({ data: responseObject }); - }; - - /** - * Base URL for intents to Authenticator. - * @const - * @private - */ - u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ = - "intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE"; - - /** - * Wrap the iOS client app with a MessagePort interface. - * @constructor - * @private - */ - u2f.WrappedIosPort_ = function () {}; - - /** - * Launch the iOS client app request - * @param {Object} message - */ - u2f.WrappedIosPort_.prototype.postMessage = function (message) { - var str = JSON.stringify(message); - var url = "u2f://auth?" + encodeURI(str); - location.replace(url); - }; - - /** - * Tells what type of port this is. - * @return {String} port type - */ - u2f.WrappedIosPort_.prototype.getPortType = function () { - return "WrappedIosPort_"; - }; - - /** - * Emulates the HTML 5 addEventListener interface. - * @param {string} eventName - * @param {function({data: Object})} handler - */ - u2f.WrappedIosPort_.prototype.addEventListener = function ( - eventName, - handler - ) { - var name = eventName.toLowerCase(); - if (name !== "message") { - console.error("WrappedIosPort only supports message"); - } - }; - - /** - * Sets up an embedded trampoline iframe, sourced from the extension. - * @param {function(MessagePort)} callback - * @private - */ - u2f.getIframePort_ = function (callback) { - // Create the iframe - var iframeOrigin = "chrome-extension://" + u2f.EXTENSION_ID; - var iframe = document.createElement("iframe"); - iframe.src = iframeOrigin + "/u2f-comms.html"; - iframe.setAttribute("style", "display:none"); - document.body.appendChild(iframe); - - var channel = new MessageChannel(); - var ready = function (message) { - if (message.data == "ready") { - channel.port1.removeEventListener("message", ready); - callback(channel.port1); - } else { - console.error('First event on iframe port was not "ready"'); - } - }; - channel.port1.addEventListener("message", ready); - channel.port1.start(); - - iframe.addEventListener("load", function () { - // Deliver the port to the iframe and initialize - iframe.contentWindow.postMessage("init", iframeOrigin, [ - channel.port2, - ]); - }); - }; - - //High-level JS API - - /** - * Default extension response timeout in seconds. - * @const - */ - u2f.EXTENSION_TIMEOUT_SEC = 30; - - /** - * A singleton instance for a MessagePort to the extension. - * @type {MessagePort|u2f.WrappedChromeRuntimePort_} - * @private - */ - u2f.port_ = null; - - /** - * Callbacks waiting for a port - * @type {Array} - * @private - */ - u2f.waitingForPort_ = []; - - /** - * A counter for requestIds. - * @type {number} - * @private - */ - u2f.reqCounter_ = 0; - - /** - * A map from requestIds to client callbacks - * @type {Object.} - * @private - */ - u2f.callbackMap_ = {}; - - /** - * Creates or retrieves the MessagePort singleton to use. - * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback - * @private - */ - u2f.getPortSingleton_ = function (callback) { - if (u2f.port_) { - callback(u2f.port_); - } else { - if (u2f.waitingForPort_.length == 0) { - u2f.getMessagePort(function (port) { - u2f.port_ = port; - u2f.port_.addEventListener( - "message", - /** @type {function(Event)} */ (u2f.responseHandler_) - ); - - // Careful, here be async callbacks. Maybe. - while (u2f.waitingForPort_.length) - u2f.waitingForPort_.shift()(u2f.port_); - }); - } - u2f.waitingForPort_.push(callback); - } - }; - - /** - * Handles response messages from the extension. - * @param {MessageEvent.} message - * @private - */ - u2f.responseHandler_ = function (message) { - var response = message.data; - var reqId = response["requestId"]; - if (!reqId || !u2f.callbackMap_[reqId]) { - console.error("Unknown or missing requestId in response."); - return; - } - var cb = u2f.callbackMap_[reqId]; - delete u2f.callbackMap_[reqId]; - cb(response["responseData"]); - }; - - /** - * Dispatches an array of sign requests to available U2F tokens. - * If the JS API version supported by the extension is unknown, it first sends a - * message to the extension to find out the supported API version and then it sends - * the sign request. - * @param {string=} appId - * @param {string=} challenge - * @param {Array} registeredKeys - * @param {function((u2f.Error|u2f.SignResponse))} callback - * @param {number=} opt_timeoutSeconds - */ - u2f.sign = function ( - appId, - challenge, - registeredKeys, - callback, - opt_timeoutSeconds - ) { - if (js_api_version === undefined) { - // Send a message to get the extension to JS API version, then send the actual sign request. - u2f.getApiVersion(function (response) { - js_api_version = - response["js_api_version"] === undefined - ? 0 - : response["js_api_version"]; - console.log("Extension JS API Version: ", js_api_version); - u2f.sendSignRequest( - appId, - challenge, - registeredKeys, - callback, - opt_timeoutSeconds - ); - }); - } else { - // We know the JS API version. Send the actual sign request in the supported API version. - u2f.sendSignRequest( - appId, - challenge, - registeredKeys, - callback, - opt_timeoutSeconds - ); - } - }; - - /** - * Dispatches an array of sign requests to available U2F tokens. - * @param {string=} appId - * @param {string=} challenge - * @param {Array} registeredKeys - * @param {function((u2f.Error|u2f.SignResponse))} callback - * @param {number=} opt_timeoutSeconds - */ - u2f.sendSignRequest = function ( - appId, - challenge, - registeredKeys, - callback, - opt_timeoutSeconds - ) { - u2f.getPortSingleton_(function (port) { - var reqId = ++u2f.reqCounter_; - u2f.callbackMap_[reqId] = callback; - var timeoutSeconds = - typeof opt_timeoutSeconds !== "undefined" - ? opt_timeoutSeconds - : u2f.EXTENSION_TIMEOUT_SEC; - var req = u2f.formatSignRequest_( - appId, - challenge, - registeredKeys, - timeoutSeconds, - reqId - ); - port.postMessage(req); - }); - }; - - /** - * Dispatches register requests to available U2F tokens. An array of sign - * requests identifies already registered tokens. - * If the JS API version supported by the extension is unknown, it first sends a - * message to the extension to find out the supported API version and then it sends - * the register request. - * @param {string=} appId - * @param {Array} registerRequests - * @param {Array} registeredKeys - * @param {function((u2f.Error|u2f.RegisterResponse))} callback - * @param {number=} opt_timeoutSeconds - */ - u2f.register = function ( - appId, - registerRequests, - registeredKeys, - callback, - opt_timeoutSeconds - ) { - if (js_api_version === undefined) { - // Send a message to get the extension to JS API version, then send the actual register request. - u2f.getApiVersion(function (response) { - js_api_version = - response["js_api_version"] === undefined - ? 0 - : response["js_api_version"]; - console.log("Extension JS API Version: ", js_api_version); - u2f.sendRegisterRequest( - appId, - registerRequests, - registeredKeys, - callback, - opt_timeoutSeconds - ); - }); - } else { - // We know the JS API version. Send the actual register request in the supported API version. - u2f.sendRegisterRequest( - appId, - registerRequests, - registeredKeys, - callback, - opt_timeoutSeconds - ); - } - }; - - /** - * Dispatches register requests to available U2F tokens. An array of sign - * requests identifies already registered tokens. - * @param {string=} appId - * @param {Array} registerRequests - * @param {Array} registeredKeys - * @param {function((u2f.Error|u2f.RegisterResponse))} callback - * @param {number=} opt_timeoutSeconds - */ - u2f.sendRegisterRequest = function ( - appId, - registerRequests, - registeredKeys, - callback, - opt_timeoutSeconds - ) { - u2f.getPortSingleton_(function (port) { - var reqId = ++u2f.reqCounter_; - u2f.callbackMap_[reqId] = callback; - var timeoutSeconds = - typeof opt_timeoutSeconds !== "undefined" - ? opt_timeoutSeconds - : u2f.EXTENSION_TIMEOUT_SEC; - var req = u2f.formatRegisterRequest_( - appId, - registeredKeys, - registerRequests, - timeoutSeconds, - reqId - ); - port.postMessage(req); - }); - }; - - /** - * Dispatches a message to the extension to find out the supported - * JS API version. - * If the user is on a mobile phone and is thus using Google Authenticator instead - * of the Chrome extension, don't send the request and simply return 0. - * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback - * @param {number=} opt_timeoutSeconds - */ - u2f.getApiVersion = function (callback, opt_timeoutSeconds) { - u2f.getPortSingleton_(function (port) { - // If we are using Android Google Authenticator or iOS client app, - // do not fire an intent to ask which JS API version to use. - if (port.getPortType) { - var apiVersion; - switch (port.getPortType()) { - case "WrappedIosPort_": - case "WrappedAuthenticatorPort_": - apiVersion = 1.1; - break; - - default: - apiVersion = 0; - break; - } - callback({ js_api_version: apiVersion }); - return; - } - var reqId = ++u2f.reqCounter_; - u2f.callbackMap_[reqId] = callback; - var req = { - type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST, - timeoutSeconds: - typeof opt_timeoutSeconds !== "undefined" - ? opt_timeoutSeconds - : u2f.EXTENSION_TIMEOUT_SEC, - requestId: reqId, - }; - port.postMessage(req); - }); - }; -})(); diff --git a/views/src/popup/popup.hbs b/views/src/popup/popup.hbs deleted file mode 100644 index d9ccea1..0000000 --- a/views/src/popup/popup.hbs +++ /dev/null @@ -1,24 +0,0 @@ - - - - - Popup Auth - - - - - -
    -
    -

    Allow LOADING... to authenticate you?

    -
    -
    -
    - - -
    -
    -
    - - - diff --git a/views/src/popup/popup.js b/views/src/popup/popup.js deleted file mode 100644 index 0e70e44..0000000 --- a/views/src/popup/popup.js +++ /dev/null @@ -1,32 +0,0 @@ -async function getJWT(client_id, hostname) { - hostname = encodeURIComponent(hostname); - client_id = encodeURIComponent(client_id); - - const res = await fetch( - `/api/user/jwt?client_id=${client_id}&origin=${hostname}` - ).then((res) => res.json()); - - return res; -} - -let acceptPromise; - -window.allow = () => acceptPromise(); -window.deny = () => window.close(); - -function start() { - let started = false; - window.addEventListener("message", async (msg) => { - if (!started) { - started = true; - const url = new URL(msg.origin); - document.getElementById("hostname").innerText = url.hostname; - await new Promise((yes) => (acceptPromise = yes)); - const res = await getJWT(msg.data.client_id, url.hostname); - msg.source.postMessage(res, msg.origin); - window.close(); - } - }); -} - -start(); diff --git a/views/src/popup/popup.scss b/views/src/popup/popup.scss deleted file mode 100644 index 5811b95..0000000 --- a/views/src/popup/popup.scss +++ /dev/null @@ -1,63 +0,0 @@ -@import "@material/button/mdc-button"; -.blue-button { - background: #4a89dc !important; -} - -.grey-button { - background: #797979 !important; -} - -hr { - // display: block; - // height: 1px; - border: 0; - border-top: 1px solid #b8b8b8; - // margin: 1em 0; - // padding: 0; -} -body { - font-family: Helvetica; - background: #eee; - -webkit-font-smoothing: antialiased; -} - -.title { - text-align: center; -} - -h1, -h3 { - font-weight: 300; -} - -h1 { - color: #636363; -} - -ul { - list-style: none; - padding-left: 0; -} - -.scope_title { - margin-top: 0; - margin-bottom: 0; - padding-left: 5px; -} - -.scope_description { - margin-top: 0; - padding-left: 15px; - font-size: 13px; - color: #202020; -} - -.card { - max-width: 480px; - margin: 4em auto; - padding: 3em 2em 2em 2em; - background: #fafafa; - border: 1px solid #ebebeb; - box-shadow: rgba(0, 0, 0, 0.14902) 0px 1px 1px 0px, - rgba(0, 0, 0, 0.09804) 0px 1px 2px 0px; -} diff --git a/views_repo b/views_repo index 4191522..484be5a 160000 --- a/views_repo +++ b/views_repo @@ -1 +1 @@ -Subproject commit 4191522b24334f20f7dcda811ca43959b02fef7a +Subproject commit 484be5a04879ec78c9ab902c92fc166d80dc7f41