diff --git a/.env.development b/.env.development
index 6126860..764926d 100644
--- a/.env.development
+++ b/.env.development
@@ -1,4 +1,5 @@
VITE_SSO_ORIGIN=http://localhost:9090
+VITE_OAUTH2_CLIENT_ID=abc123
VITE_API_VIOLET=http://localhost:9095/v1/violet
VITE_API_ORCHID=http://localhost:9095/v1/orchid
diff --git a/.env.production b/.env.production
index 88f5f35..bedb04d 100644
--- a/.env.production
+++ b/.env.production
@@ -1,4 +1,5 @@
VITE_SSO_ORIGIN=https://sso.1f349.com
+VITE_OAUTH2_CLIENT_ID=9b11a141-bcb8-4140-9c88-531a5d7bf15d
VITE_API_VIOLET=https://api.1f349.com/v1/violet
VITE_API_ORCHID=https://api.1f349.com/v1/orchid
diff --git a/src/App.svelte b/src/App.svelte
index fb8cf94..9ae3ba9 100644
--- a/src/App.svelte
+++ b/src/App.svelte
@@ -6,9 +6,8 @@
import CertificatesView from "./views/CertificatesView.svelte";
import SitesView from "./views/SitesView.svelte";
import {loginStore, parseJwt, type LoginStore} from "./stores/login";
- import {openLoginPopup} from "./utils/login-popup";
import {domainOption} from "./stores/domain-option";
- import {apiVerify} from "./utils/api-request";
+ import {LOGIN} from "./utils/login";
let sidebarOptions: Array<{name: string; view: typeof SvelteComponent<{}>}> = [
{name: "General", view: GeneralView},
@@ -34,7 +33,8 @@
}
onMount(() => {
- apiVerify().catch(() => {});
+ LOGIN.init();
+ LOGIN.userinfo(false);
});
@@ -48,7 +48,7 @@
{#if $loginStore == null}
-
+
{:else}
@@ -58,6 +58,7 @@
on:click={() => {
$loginStore = null;
localStorage.removeItem("login-session");
+ localStorage.removeItem("pop2_access_token");
}}
>
Logout
diff --git a/src/utils/api-request.ts b/src/utils/api-request.ts
deleted file mode 100644
index b0e1167..0000000
--- a/src/utils/api-request.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import {get} from "svelte/store";
-import {getBearer, loginStore} from "../stores/login";
-
-const TOKEN_VERIFY_API = import.meta.env.VITE_SSO_ORIGIN + "/verify";
-const TOKEN_REFRESH_API = import.meta.env.VITE_SSO_ORIGIN + "/refresh";
-
-export async function apiRequest(url: string, init?: RequestInit): Promise {
- // setup authorization header
- if (init == undefined) init = {};
- init.headers = {...init.headers, Authorization: getBearer()};
-
- let f = await fetch(url, init);
- if (f.status !== 403) return f;
-
- let refreshResp = await fetch(TOKEN_REFRESH_API, {
- method: "POST",
- mode: "cors",
- cache: "no-cache",
- credentials: "include",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({token: get(loginStore)?.tokens.refresh}),
- });
- if (refreshResp.status !== 200) {
- loginStore.set(null);
- alert("Failed to refresh login session: please login again to continue");
- throw new Error("403 Unauthorized");
- }
- let refreshJson = await refreshResp.json();
- loginStore.set(refreshJson);
-
- // update current authorization header
- init.headers = {...init.headers, Authorization: getBearer()};
- return await fetch(url, init);
-}
-
-export async function apiVerify() {
- return await apiRequest(TOKEN_VERIFY_API, {
- method: "POST",
- mode: "cors",
- cache: "no-cache",
- });
-}
diff --git a/src/utils/login-popup.ts b/src/utils/login-popup.ts
deleted file mode 100644
index 79064f0..0000000
--- a/src/utils/login-popup.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import {loginStore} from "../stores/login";
-
-let currentLoginPopup: {close: () => void} | null = null;
-
-const ssoOrigin = import.meta.env.VITE_SSO_ORIGIN;
-
-window.addEventListener("message", function (event) {
- if (event.origin !== ssoOrigin) return;
- if (isObject(event.data)) {
- loginStore.set(event.data);
- if (currentLoginPopup) currentLoginPopup.close();
- return;
- }
- alert("Failed to log user in: the login data was probably corrupted");
-});
-
-function isObject(obj: Object) {
- return obj != null && obj.constructor.name === "Object";
-}
-
-function popupCenterScreen(url: string, title: string, w: number, h: number, focus: boolean) {
- const top = (screen.availHeight - h) / 4,
- left = (screen.availWidth - w) / 2;
- const popup = openWindow(url, title, `scrollbars=yes,width=${w},height=${h},top=${top},left=${left}`);
- if (focus === true && window.focus !== undefined && popup !== null) popup.focus();
- return popup;
-}
-
-function openWindow(url: string | URL, winnm: string, options: string): Window | null {
- var wTop = firstAvailableValue([window.screen.availTop, window.screenY, window.screenTop, 0]);
- var wLeft = firstAvailableValue([window.screen.availLeft, window.screenX, window.screenLeft, 0]);
- let w: Window | null;
- var top = 0,
- left = 0;
- var result;
- if ((result = /top=(\d+)/g.exec(options))) top = parseInt(result[1]);
- if ((result = /left=(\d+)/g.exec(options))) left = parseInt(result[1]);
- if (options) {
- options = options.replace("top=" + top, "top=" + (top + wTop));
- options = options.replace("left=" + left, "left=" + (left + wLeft));
- w = window.open(url, winnm, options);
- } else w = window.open(url, winnm);
- return w;
-}
-
-function firstAvailableValue(arr: any) {
- for (var i = 0; i < arr.length; i++) if (typeof arr[i] != "undefined") return arr[i];
-}
-
-export function openLoginPopup() {
- if (currentLoginPopup) currentLoginPopup.close();
- currentLoginPopup = popupCenterScreen(ssoOrigin + "/popup?origin=" + encodeURIComponent(location.origin), "Login with 1f349 SSO", 500, 500, false);
-}
diff --git a/src/utils/login.ts b/src/utils/login.ts
new file mode 100644
index 0000000..985d0b1
--- /dev/null
+++ b/src/utils/login.ts
@@ -0,0 +1,33 @@
+import {get} from "svelte/store";
+import {getBearer, loginStore} from "../stores/login";
+import {POP2} from "./pop2";
+
+const TOKEN_AUTHORIZE_API = import.meta.env.VITE_SSO_ORIGIN + "/authorize";
+const TOKEN_USERINFO_API = import.meta.env.VITE_SSO_ORIGIN + "/userinfo";
+const OAUTH2_CLIENT_ID = import.meta.env.VITE_OAUTH2_CLIENT_ID;
+
+export const LOGIN = {
+ init: () => {
+ POP2.init(TOKEN_AUTHORIZE_API, OAUTH2_CLIENT_ID, "openid profile name", 500, 600);
+ },
+ clientRequest: (resource: string, options: RequestInit, refresh: boolean) => {
+ return POP2.clientRequest(resource, options, refresh);
+ },
+ userinfo: (popup: boolean) => {
+ console.info("userinfo", popup);
+ POP2.getToken((token: string) => {
+ POP2.clientRequest(TOKEN_USERINFO_API, {}, popup)
+ .then(x => x.json())
+ .then(x => {
+ console.log(token, x);
+ loginStore.set({
+ userinfo: x,
+ tokens: {access: token, refresh: ""},
+ });
+ })
+ .catch(x => {
+ console.error(x);
+ });
+ }, popup);
+ },
+};
diff --git a/src/utils/pop2.js b/src/utils/pop2.js
new file mode 100644
index 0000000..b2b772b
--- /dev/null
+++ b/src/utils/pop2.js
@@ -0,0 +1,187 @@
+/* Simple OAuth 2.0 Client flow library
+
+ Author: MrMelon54, timdream
+
+ Usage:
+ POP2.init(client_id, scope)
+ Initialize the library.
+ redirect_uri is the current page (window.location.href).
+ This function should be put before Analytics so that the second click won't result a page view register.
+ POP2.getToken(callback)
+ Send access token to the callback function as the first argument.
+ If not logged in this triggers login popup and execute login after logged in.
+ Be sure to call this function in user-triggered event (such as click) to prevent popup blocker.
+ If not sure do use isLoggedIn() below to check first.
+ POP2.isLoggedIn()
+ boolean
+
+*/
+
+"use strict";
+
+export const POP2 = (function (w) {
+ const windowName = "pop2_oauth2_login_popup";
+
+ if (window.name === windowName) {
+ if (window.opener && window.opener.POP2) {
+ if (window.location.hash.indexOf("access_token") !== -1) {
+ window.opener.POP2.receiveToken(
+ window.location.hash.replace(/^.*access_token=([^&]+).*$/, "$1"),
+ parseInt(window.location.hash.replace(/^.*expires_in=([^&]+).*$/, "$1")),
+ );
+ }
+ if (window.location.search.indexOf("error=")) {
+ window.opener.POP2.receiveToken("ERROR");
+ }
+ }
+ window.close();
+ }
+
+ function popupCenterScreen(url, title, w, h) {
+ const top = (screen.availHeight - h) / 4,
+ left = (screen.availWidth - w) / 2;
+ return openWindow(url, title, `scrollbars=yes,width=${w},height=${h},top=${top},left=${left}`);
+ }
+
+ function openWindow(url, winnm, options) {
+ const wTop = firstAvailableValue([window.screen.availTop, window.screenY, window.screenTop, 0]);
+ const wLeft = firstAvailableValue([window.screen.availLeft, window.screenX, window.screenLeft, 0]);
+ let top = "0",
+ left = "0",
+ result;
+ if ((result = /top=(\d+)/g.exec(options))) top = parseInt(result[1]);
+ if ((result = /left=(\d+)/g.exec(options))) left = parseInt(result[1]);
+ let w;
+ if (options) {
+ options = options.replace("top=" + top, "top=" + (parseInt(top) + wTop));
+ options = options.replace("left=" + left, "left=" + (parseInt(left) + wLeft));
+ w = window.open(url, winnm, options);
+ } else w = window.open(url, winnm);
+ return w;
+ }
+
+ function firstAvailableValue(arr) {
+ for (let i = 0; i < arr.length; i++) if (typeof arr[i] != "undefined") return arr[i];
+ }
+
+ let client_endpoint,
+ client_id,
+ scope = "",
+ redirect_uri = window.location.href.substr(0, window.location.href.length - window.location.hash.length).replace(/#$/, ""),
+ access_token = localStorage.getItem("pop2_access_token"),
+ callbackWaitForToken,
+ w_width = 400,
+ w_height = 360;
+
+ const POP2 = {
+ // init
+ init: function (f_client_endpoint, f_client_id, f_scope, width, height) {
+ if (!f_client_endpoint) return false;
+ if (!f_client_id) return false;
+ client_endpoint = f_client_endpoint;
+ client_id = f_client_id;
+ if (f_scope) scope = f_scope;
+ if (width) w_width = width;
+ if (height) w_height = height;
+ },
+ // receive token from popup
+ receiveToken: function (token, expires_in) {
+ if (token !== "ERROR") {
+ access_token = token;
+ localStorage.setItem("pop2_access_token", access_token);
+ if (callbackWaitForToken) callbackWaitForToken(access_token);
+ setTimeout(function () {
+ access_token = undefined;
+ localStorage.removeItem("pop2_access_token");
+ }, expires_in * 1000);
+ } else if (token === false) {
+ callbackWaitForToken = undefined;
+ }
+ },
+ // pass the access token to callback
+ // if not logged in this triggers login popup;
+ // use isLoggedIn to check login first to prevent popup blocker
+ getToken: function (callback, popup = true) {
+ if (!client_id || !redirect_uri || !scope) {
+ alert("You need init() first. Check the program flow.");
+ return false;
+ }
+ if (!popup) throw Error("missing access token");
+ if (!access_token) {
+ callbackWaitForToken = callback;
+ popupCenterScreen(
+ client_endpoint +
+ "?response_type=token" +
+ "&redirect_uri=" +
+ encodeURIComponent(redirect_uri) +
+ "&scope=" +
+ encodeURIComponent(scope) +
+ "&client_id=" +
+ encodeURIComponent(client_id),
+ windowName,
+ w_width,
+ w_height,
+ );
+ return false;
+ } else {
+ callback(access_token);
+ return true;
+ }
+ },
+ clientRequest: function (resource, options, refresh = false) {
+ const sendRequest = function () {
+ options.credentials = "include";
+ if (!options.headers) options.headers = {};
+ options.headers["Authorization"] = "Bearer " + access_token;
+ return new Promise(function (res, rej) {
+ fetch(resource, options)
+ .then(function (x) {
+ if (x.status >= 200 && x.status < 300) res(x);
+ else rej(x);
+ })
+ .catch(function (x) {
+ rej(["failed to send request", x]);
+ });
+ });
+ };
+ const resendRequest = function () {
+ return new Promise(function (res, rej) {
+ access_token = undefined;
+ POP2.getToken(function () {
+ sendRequest()
+ .then(function (x) {
+ res(x);
+ })
+ .catch(function (x) {
+ rej(["failed to resend request", x]);
+ });
+ });
+ });
+ };
+
+ if (!refresh) {
+ if (!access_token) return Promise.reject("missing access token");
+ return sendRequest();
+ } else {
+ return new Promise(function (res, rej) {
+ sendRequest()
+ .then(function (x) {
+ res(x);
+ })
+ .catch(function () {
+ resendRequest()
+ .then(function (x) {
+ res(x);
+ })
+ .catch(function (x) {
+ rej(x);
+ });
+ });
+ });
+ }
+ },
+ };
+
+ window.POP2 = POP2;
+ return POP2;
+})();
diff --git a/src/views/CertificatesView.svelte b/src/views/CertificatesView.svelte
index 9a7ed07..b737c7b 100644
--- a/src/views/CertificatesView.svelte
+++ b/src/views/CertificatesView.svelte
@@ -1,7 +1,7 @@