mirror of
https://github.com/1f349/admin.1f349.com.git
synced 2025-02-22 13:34:57 +00:00
Add apiRequest function to auto refresh outdated access token
This commit is contained in:
parent
3d12a4dda3
commit
47bf91e7ff
35
src/utils/api-request.ts
Normal file
35
src/utils/api-request.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import {get} from "svelte/store";
|
||||||
|
import {getBearer, loginStore} from "../stores/login";
|
||||||
|
|
||||||
|
const TOKEN_REFRESH_API = import.meta.env.VITE_SSO_ORIGIN + "/refresh";
|
||||||
|
|
||||||
|
export async function apiRequest(url: string, init?: RequestInit): Promise<Response> {
|
||||||
|
// 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);
|
||||||
|
}
|
@ -2,6 +2,8 @@
|
|||||||
import {domainOption} from "../stores/domain-option";
|
import {domainOption} from "../stores/domain-option";
|
||||||
import {getBearer} from "../stores/login";
|
import {getBearer} from "../stores/login";
|
||||||
import {type Cert, certsTable} from "../stores/certs";
|
import {type Cert, certsTable} from "../stores/certs";
|
||||||
|
import {apiRequest} from "../utils/api-request";
|
||||||
|
import {onMount} from "svelte";
|
||||||
|
|
||||||
const apiOrchid = import.meta.env.VITE_API_ORCHID;
|
const apiOrchid = import.meta.env.VITE_API_ORCHID;
|
||||||
|
|
||||||
@ -27,25 +29,17 @@
|
|||||||
return p.endsWith(domain);
|
return p.endsWith(domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
let promiseForTable: Promise<void> = Object.entries($certsTable).length === 0 ? reloadTable() : Promise.resolve();
|
let promiseForTable: Promise<void> = Object.entries($certsTable).length === 0 ? reloadTable() : reloadTable();
|
||||||
|
|
||||||
function reloadTable(): Promise<void> {
|
async function reloadTable(): Promise<void> {
|
||||||
return new Promise<void>((res, rej) => {
|
let f = await apiRequest(apiOrchid + "/owned");
|
||||||
fetch(apiOrchid + "/owned", {headers: {Authorization: getBearer()}})
|
if (f.status !== 200) throw new Error("Unexpected status code: " + f.status);
|
||||||
.then(x => {
|
let fJson = await f.json();
|
||||||
if (x.status !== 200) throw new Error("Unexpected status code: " + x.status);
|
let rows = fJson as Map<number, Cert>;
|
||||||
return x.json();
|
Object.values(rows).forEach(x => {
|
||||||
})
|
$certsTable[Object(x.id).toString()] = x;
|
||||||
.then(x => {
|
|
||||||
let rows = x as Map<number, Cert>;
|
|
||||||
Object.values(rows).forEach(x => {
|
|
||||||
$certsTable[Object(x.id).toString()] = x;
|
|
||||||
});
|
|
||||||
console.log($certsTable);
|
|
||||||
res();
|
|
||||||
})
|
|
||||||
.catch(x => rej(x));
|
|
||||||
});
|
});
|
||||||
|
console.log($certsTable);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,5 +1,29 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {loginStore, parseJwt} from "../stores/login";
|
||||||
|
|
||||||
|
let tokenExp = 0;
|
||||||
|
let diffExp = 0;
|
||||||
|
|
||||||
|
loginStore.subscribe(x => {
|
||||||
|
if (!x) return;
|
||||||
|
let jwt = parseJwt(x?.tokens.access);
|
||||||
|
tokenExp = jwt.exp;
|
||||||
|
});
|
||||||
|
|
||||||
|
function timeDiff(exp: number) {
|
||||||
|
if (exp === 0) return 0;
|
||||||
|
return exp * 1000 - new Date().getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
diffExp = timeDiff(tokenExp);
|
||||||
|
}, 500);
|
||||||
|
</script>
|
||||||
|
|
||||||
<div style="padding:8px;background-color:#bb7900;">Warning: This is currently still under development</div>
|
<div style="padding:8px;background-color:#bb7900;">Warning: This is currently still under development</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<a class="btn-green" href="https://uptime-kuma.1f349.com" target="_blank">Status Dashboard</a>
|
<a class="btn-green" href="https://uptime-kuma.1f349.com" target="_blank">Status Dashboard</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>{diffExp === 0 ? "No token" : diffExp}</div>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import {domainOption} from "../stores/domain-option";
|
import {domainOption} from "../stores/domain-option";
|
||||||
import {getBearer} from "../stores/login";
|
import {getBearer} from "../stores/login";
|
||||||
import {type Site, sitesTable} from "../stores/sites";
|
import {type Site, sitesTable} from "../stores/sites";
|
||||||
|
import {apiRequest} from "../utils/api-request";
|
||||||
|
|
||||||
const apiSiteHosting = import.meta.env.VITE_API_SITE_HOSTING;
|
const apiSiteHosting = import.meta.env.VITE_API_SITE_HOSTING;
|
||||||
|
|
||||||
@ -22,51 +23,33 @@
|
|||||||
|
|
||||||
let promiseForTable: Promise<void> = Object.entries($sitesTable).length === 0 ? reloadTable() : Promise.resolve();
|
let promiseForTable: Promise<void> = Object.entries($sitesTable).length === 0 ? reloadTable() : Promise.resolve();
|
||||||
|
|
||||||
function reloadTable(): Promise<void> {
|
async function reloadTable(): Promise<void> {
|
||||||
return new Promise<void>((res, rej) => {
|
let f = await apiRequest(apiSiteHosting);
|
||||||
fetch(apiSiteHosting, {headers: {Authorization: getBearer()}})
|
if (f.status !== 200) throw new Error("Unexpected status code: " + f.status);
|
||||||
.then(x => {
|
let fJson = await f.json();
|
||||||
if (x.status !== 200) throw new Error("Unexpected status code: " + x.status);
|
let rows = fJson as Site[];
|
||||||
return x.json();
|
rows.forEach(x => {
|
||||||
})
|
$sitesTable[x.domain] = x;
|
||||||
.then(x => {
|
|
||||||
let rows = x as Site[];
|
|
||||||
rows.forEach(x => {
|
|
||||||
$sitesTable[x.domain] = x;
|
|
||||||
});
|
|
||||||
res();
|
|
||||||
})
|
|
||||||
.catch(x => rej(x));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteBranch(site: Site, branch: string) {
|
async function deleteBranch(site: Site, branch: string) {
|
||||||
fetch(apiSiteHosting, {
|
let f = await apiRequest(apiSiteHosting, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {Authorization: getBearer()},
|
|
||||||
body: JSON.stringify({submit: "delete-branch", site: site.domain, branch}),
|
body: JSON.stringify({submit: "delete-branch", site: site.domain, branch}),
|
||||||
})
|
});
|
||||||
.then(x => {
|
if (f.status !== 200) throw new Error("Unexpected status code: " + f.status);
|
||||||
if (x.status !== 200) throw new Error("Unexpected status code: " + x.status);
|
promiseForTable = reloadTable();
|
||||||
promiseForTable = reloadTable();
|
|
||||||
})
|
|
||||||
.catch(x => alert("Error deleting branch: " + x));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetSiteSecret(site: Site) {
|
async function resetSiteSecret(site: Site) {
|
||||||
fetch(apiSiteHosting, {
|
let f = await apiRequest(apiSiteHosting, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {Authorization: getBearer()},
|
|
||||||
body: JSON.stringify({submit: "secret", site: site.domain}),
|
body: JSON.stringify({submit: "secret", site: site.domain}),
|
||||||
})
|
});
|
||||||
.then(x => {
|
if (f.status !== 200) throw new Error("Unexpected status code: " + f.status);
|
||||||
if (x.status !== 200) throw new Error("Unexpected status code: " + x.status);
|
let fJson = await f.json();
|
||||||
return x.json();
|
alert("New secret: " + fJson.secret);
|
||||||
})
|
|
||||||
.then(x => {
|
|
||||||
alert("New secret: " + x.secret);
|
|
||||||
})
|
|
||||||
.catch(x => alert("Error resetting secret: " + x));
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import {apiRequest} from "../utils/api-request";
|
||||||
|
|
||||||
import {writable, type Writable} from "svelte/store";
|
import {writable, type Writable} from "svelte/store";
|
||||||
|
|
||||||
import {getBearer} from "../stores/login";
|
import {getBearer} from "../stores/login";
|
||||||
@ -39,30 +41,23 @@
|
|||||||
|
|
||||||
let promiseForTable: Promise<void> = Object.keys($tableData).length === 0 ? reloadTable() : Promise.resolve();
|
let promiseForTable: Promise<void> = Object.keys($tableData).length === 0 ? reloadTable() : Promise.resolve();
|
||||||
|
|
||||||
function reloadTable(): Promise<void> {
|
async function reloadTable(): Promise<void> {
|
||||||
return new Promise<void>((res, rej) => {
|
let f = await apiRequest(apiUrl);
|
||||||
fetch(apiUrl, {headers: {Authorization: getBearer()}})
|
if (f.status !== 200) throw new Error("Unexpected status code: " + f.status);
|
||||||
.then(x => {
|
let fJson = await f.json();
|
||||||
if (x.status !== 200) throw new Error("Unexpected status code: " + x.status);
|
|
||||||
return x.json();
|
let rows = fJson as T[];
|
||||||
})
|
let srcs = new Set(Object.keys($tableData));
|
||||||
.then(x => {
|
rows.forEach(x => {
|
||||||
let rows = x as T[];
|
$tableData[x.src] = {
|
||||||
let srcs = new Set(Object.keys($tableData));
|
client: !$tableData[x.src] ? JSON.parse(JSON.stringify(x)) : $tableData[x.src]?.client,
|
||||||
rows.forEach(x => {
|
server: x,
|
||||||
$tableData[x.src] = {
|
p: Promise.resolve(),
|
||||||
client: !$tableData[x.src] ? JSON.parse(JSON.stringify(x)) : $tableData[x.src]?.client,
|
};
|
||||||
server: x,
|
srcs.delete(x.src);
|
||||||
p: Promise.resolve(),
|
});
|
||||||
};
|
srcs.forEach(x => {
|
||||||
srcs.delete(x.src);
|
$tableData[x].server = null;
|
||||||
});
|
|
||||||
srcs.forEach(x => {
|
|
||||||
$tableData[x].server = null;
|
|
||||||
});
|
|
||||||
res();
|
|
||||||
})
|
|
||||||
.catch(x => rej(x));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ require (
|
|||||||
require github.com/becheran/wildmatch-go v1.0.0 // indirect
|
require github.com/becheran/wildmatch-go v1.0.0 // indirect
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/1f349/mjwt v0.2.0
|
github.com/1f349/mjwt v0.2.1
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
github.com/1f349/mjwt v0.2.0 h1:1c3+J05RRBsClGxA91SzT3I2DkwasGA4OgLcIeXWmq4=
|
github.com/1f349/mjwt v0.2.0 h1:1c3+J05RRBsClGxA91SzT3I2DkwasGA4OgLcIeXWmq4=
|
||||||
github.com/1f349/mjwt v0.2.0/go.mod h1:KEs6jd9JjWrQW+8feP2pGAU7pdA3aYTqjkT/YQr73PU=
|
github.com/1f349/mjwt v0.2.0/go.mod h1:KEs6jd9JjWrQW+8feP2pGAU7pdA3aYTqjkT/YQr73PU=
|
||||||
|
github.com/1f349/mjwt v0.2.1 h1:REdiM/MaNjYQwHvI39LaMPhlvMg4Vy9SgomWMsKTNz8=
|
||||||
|
github.com/1f349/mjwt v0.2.1/go.mod h1:KEs6jd9JjWrQW+8feP2pGAU7pdA3aYTqjkT/YQr73PU=
|
||||||
github.com/MrMelon54/mjwt v0.1.1 h1:m+aTpxbhQCrOPKHN170DQMFR5r938LkviU38unob5Jw=
|
github.com/MrMelon54/mjwt v0.1.1 h1:m+aTpxbhQCrOPKHN170DQMFR5r938LkviU38unob5Jw=
|
||||||
github.com/MrMelon54/mjwt v0.1.1/go.mod h1:oYrDBWK09Hju98xb+bRQ0wy+RuAzacxYvKYOZchR2Tk=
|
github.com/MrMelon54/mjwt v0.1.1/go.mod h1:oYrDBWK09Hju98xb+bRQ0wy+RuAzacxYvKYOZchR2Tk=
|
||||||
github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA=
|
github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA=
|
||||||
|
@ -38,7 +38,7 @@ func ssoServer(signer mjwt.Signer) {
|
|||||||
ps.Set("violet:redirect")
|
ps.Set("violet:redirect")
|
||||||
ps.Set("domain:owns=example.com")
|
ps.Set("domain:owns=example.com")
|
||||||
ps.Set("domain:owns=example.org")
|
ps.Set("domain:owns=example.org")
|
||||||
accessToken, err := signer.GenerateJwt("81b99bd7-bf74-4cc2-9133-80ed2393dfe6", uuid.NewString(), jwt.ClaimStrings{"d0555671-df9d-42d0-a4d6-94b694251f0b"}, 15*time.Minute, auth.AccessTokenClaims{
|
accessToken, err := signer.GenerateJwt("81b99bd7-bf74-4cc2-9133-80ed2393dfe6", uuid.NewString(), jwt.ClaimStrings{"d0555671-df9d-42d0-a4d6-94b694251f0b"}, 10*time.Second, auth.AccessTokenClaims{
|
||||||
Perms: ps,
|
Perms: ps,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -84,6 +84,49 @@ func ssoServer(signer mjwt.Signer) {
|
|||||||
</html>
|
</html>
|
||||||
`, accessToken, "")
|
`, accessToken, "")
|
||||||
})
|
})
|
||||||
|
var corsAccessControl = cors.New(cors.Options{
|
||||||
|
AllowOriginFunc: func(origin string) bool {
|
||||||
|
println(origin)
|
||||||
|
return origin == "http://localhost:5173"
|
||||||
|
},
|
||||||
|
AllowedMethods: []string{http.MethodPost, http.MethodOptions},
|
||||||
|
AllowedHeaders: []string{"Content-Type"},
|
||||||
|
AllowCredentials: true,
|
||||||
|
})
|
||||||
|
r.HandleFunc("/refresh", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
corsAccessControl.ServeHTTP(w, r, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ps := claims.NewPermStorage()
|
||||||
|
ps.Set("violet:route")
|
||||||
|
ps.Set("violet:redirect")
|
||||||
|
ps.Set("domain:owns=example.com")
|
||||||
|
ps.Set("domain:owns=example.org")
|
||||||
|
accessToken, err := signer.GenerateJwt("81b99bd7-bf74-4cc2-9133-80ed2393dfe6", uuid.NewString(), jwt.ClaimStrings{"d0555671-df9d-42d0-a4d6-94b694251f0b"}, 10*time.Second, auth.AccessTokenClaims{
|
||||||
|
Perms: ps,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to generate access token", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||||
|
"target": "http://localhost:5173",
|
||||||
|
"tokens": map[string]any{
|
||||||
|
"access": accessToken,
|
||||||
|
"refresh": "",
|
||||||
|
},
|
||||||
|
"userinfo": map[string]any{
|
||||||
|
"aud": "d0555671-df9d-42d0-a4d6-94b694251f0b",
|
||||||
|
"email": "admin@localhost",
|
||||||
|
"email_verified": true,
|
||||||
|
"name": "Admin",
|
||||||
|
"preferred_username": "admin",
|
||||||
|
"sub": "81b99bd7-bf74-4cc2-9133-80ed2393dfe6",
|
||||||
|
"picture": "http://localhost:5173/1f349.svg",
|
||||||
|
"updated_at": 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
log.Println("[SSO Server]", http.ListenAndServe(":9090", r))
|
log.Println("[SSO Server]", http.ListenAndServe(":9090", r))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user