mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 16:55:37 +02:00
## Description - [x] Adds support for AWS SSO, which makes us (the team) able to use SST and update the components independently - [x] Splits the webpage into the landing page (Qwik), and Astro (the console) in charge of playing. This allows us to pass in Environment Variables to the console - ~Migrates the docs from Nuxt to Nextjs, and connects them to SST. This allows us to use Fumadocs _citation needed_ that's much more beautiful, and supports OpenApi~ - Cloudflare pages with github integration is not working on our new CF account. So we will have to push the pages deployment manually with Github actions - [x] Moves the current set up from my personal CF and AWS accounts to dedicated Nestri accounts - ## Related Issues <!-- List any related issues (e.g., "Closes #123", "Fixes #456") --> ## Type of Change - [ ] Bug fix (non-breaking change) - [x] New feature (non-breaking change) - [ ] Breaking change (fix or feature that changes existing functionality) - [x] Documentation update - [ ] Other (please describe): ## Checklist - [x] I have updated relevant documentation - [x] My code follows the project's coding style - [x] My changes generate no new warnings/errors ## Notes for Reviewers <!-- Point out areas you'd like reviewers to focus on, questions you have, or decisions that need discussion --> Please approve my PR 🥹 ## Screenshots/Demo <!-- If applicable, add screenshots or a GIF demo of your changes (especially for UI changes) --> ## Additional Context <!-- Add any other context about the pull request here -->
222 lines
6.1 KiB
TypeScript
222 lines
6.1 KiB
TypeScript
import { type Team } from "@nestri/core/team/index";
|
|
import { makePersisted } from "@solid-primitives/storage";
|
|
import { useLocation, useNavigate } from "@solidjs/router";
|
|
import { createClient } from "@openauthjs/openauth/client";
|
|
import { createInitializedContext } from "../common/context";
|
|
import { createEffect, createMemo, onMount } from "solid-js";
|
|
import { createStore, produce, reconcile } from "solid-js/store";
|
|
|
|
interface AccountInfo {
|
|
id: string;
|
|
email: string;
|
|
name: string;
|
|
access: string;
|
|
refresh: string;
|
|
avatarUrl: string;
|
|
teams: Team.Info[];
|
|
discriminator: number;
|
|
polarCustomerID: string | null;
|
|
}
|
|
|
|
interface Storage {
|
|
accounts: Record<string, AccountInfo>;
|
|
current?: string;
|
|
}
|
|
|
|
export const client = createClient({
|
|
issuer: import.meta.env.VITE_AUTH_URL,
|
|
clientID: "web",
|
|
});
|
|
|
|
export const { use: useAuth, provider: AuthProvider } =
|
|
createInitializedContext("AuthContext", () => {
|
|
const [store, setStore] = makePersisted(
|
|
createStore<Storage>({
|
|
accounts: {},
|
|
}),
|
|
{
|
|
name: "radiant.auth",
|
|
},
|
|
);
|
|
const location = useLocation();
|
|
const params = createMemo(
|
|
() => new URLSearchParams(location.hash.substring(1)),
|
|
);
|
|
const accessToken = createMemo(() => params().get("access_token"));
|
|
const refreshToken = createMemo(() => params().get("refresh_token"));
|
|
|
|
|
|
createEffect(async () => {
|
|
// if (!result.current && Object.keys(store.accounts).length) {
|
|
// result.switch(Object.keys(store.accounts)[0])
|
|
// navigate("/")
|
|
// }
|
|
})
|
|
|
|
createEffect(async () => {
|
|
if (accessToken()) return;
|
|
if (Object.keys(store.accounts).length) return;
|
|
const redirect = await client.authorize(window.location.origin, "token");
|
|
window.location.href = redirect.url
|
|
});
|
|
|
|
createEffect(async () => {
|
|
const current = store.current;
|
|
const accounts = store.accounts;
|
|
if (!current) return;
|
|
const match = accounts[current];
|
|
if (match) return;
|
|
const keys = Object.keys(accounts);
|
|
if (keys.length) {
|
|
setStore("current", keys[0]);
|
|
navigate("/");
|
|
return
|
|
}
|
|
const redirect = await client.authorize(window.location.origin, "token");
|
|
window.location.href = redirect.url
|
|
});
|
|
|
|
async function refresh() {
|
|
for (const account of [...Object.values(store.accounts)]) {
|
|
if (!account.refresh) continue;
|
|
const result = await client.refresh(account.refresh, {
|
|
access: account.access,
|
|
})
|
|
if (result.err) {
|
|
if ("id" in account)
|
|
setStore(produce((state) => {
|
|
delete state.accounts[account.id];
|
|
}))
|
|
continue
|
|
};
|
|
const tokens = result.tokens || {
|
|
access: account.access,
|
|
refresh: account.refresh,
|
|
}
|
|
fetch(import.meta.env.VITE_API_URL + "/account", {
|
|
headers: {
|
|
authorization: `Bearer ${tokens.access}`,
|
|
},
|
|
}).then(async (response) => {
|
|
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
const info = await result.data;
|
|
|
|
setStore(
|
|
"accounts",
|
|
info.id,
|
|
reconcile({
|
|
...info,
|
|
...tokens,
|
|
}),
|
|
);
|
|
}
|
|
|
|
if (!response.ok)
|
|
setStore(
|
|
produce((state) => {
|
|
delete state.accounts[account.id];
|
|
}),
|
|
);
|
|
})
|
|
}
|
|
}
|
|
|
|
onMount(async () => {
|
|
if (refreshToken() && accessToken()) {
|
|
const result = await fetch(import.meta.env.VITE_API_URL + "/account", {
|
|
headers: {
|
|
authorization: `Bearer ${accessToken()}`,
|
|
},
|
|
}).catch(() => { })
|
|
if (result?.ok) {
|
|
const response = await result.json();
|
|
const info = await response.data;
|
|
setStore(
|
|
"accounts",
|
|
info.id,
|
|
reconcile({
|
|
...info,
|
|
access: accessToken(),
|
|
refresh: refreshToken(),
|
|
}),
|
|
);
|
|
setStore("current", info.id);
|
|
}
|
|
window.location.hash = "";
|
|
}
|
|
|
|
await refresh();
|
|
})
|
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
// const bar = useCommandBar()
|
|
|
|
// bar.register("auth", async () => {
|
|
// return [
|
|
// {
|
|
// category: "Account",
|
|
// title: "Logout",
|
|
// icon: IconLogout,
|
|
// run: async (bar) => {
|
|
// result.logout();
|
|
// setStore("current", undefined);
|
|
// navigate("/");
|
|
// bar.hide()
|
|
// },
|
|
// },
|
|
// {
|
|
// category: "Add Account",
|
|
// title: "Add Account",
|
|
// icon: IconUserAdd,
|
|
// run: async () => {
|
|
// const redir = await client.authorize(window.location.origin, "token");
|
|
// window.location.href = redir.url
|
|
// bar.hide()
|
|
// },
|
|
// },
|
|
// ...result.all()
|
|
// .filter((item) => item.id !== result.current.id)
|
|
// .map((item) => ({
|
|
// category: "Account",
|
|
// title: "Switch to " + item.email,
|
|
// icon: IconUser,
|
|
// run: async () => {
|
|
// result.switch(item.id);
|
|
// navigate("/");
|
|
// bar.hide()
|
|
// },
|
|
// })),
|
|
// ]
|
|
// })
|
|
|
|
const result = {
|
|
get current() {
|
|
return store.accounts[store.current!]!;
|
|
},
|
|
switch(accountID: string) {
|
|
setStore("current", accountID);
|
|
},
|
|
all() {
|
|
return Object.values(store.accounts);
|
|
},
|
|
refresh,
|
|
logout() {
|
|
setStore(
|
|
produce((state) => {
|
|
if (!state.current) return;
|
|
delete state.accounts[state.current];
|
|
state.current = Object.keys(state.accounts)[0];
|
|
}),
|
|
);
|
|
},
|
|
get ready() {
|
|
return Boolean(!accessToken() && store.current);
|
|
},
|
|
};
|
|
return result;
|
|
}); |