aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/lib')
-rw-r--r--app/src/lib/server/sb1.ts144
1 files changed, 144 insertions, 0 deletions
diff --git a/app/src/lib/server/sb1.ts b/app/src/lib/server/sb1.ts
new file mode 100644
index 0000000..c060af5
--- /dev/null
+++ b/app/src/lib/server/sb1.ts
@@ -0,0 +1,144 @@
+import { SB1_FIN_INST, SB1_ID, SB1_REDIRECT_URI, SB1_SECRET } from "$env/static/private";
+import { eq } from "drizzle-orm";
+import { Temporal } from "temporal-polyfill";
+import { randomUUID } from "node:crypto";
+import { db } from "./db";
+import { syncSession } from "./db/schema";
+
+type Sb1Tokens = {
+ access_token: string
+ expires_in: number
+ refresh_token_expires_in: number
+ refresh_token_absolute_expires_in: number
+ token_type: string
+ refresh_token: string
+}
+
+type Transaction = {
+ description: string
+ amount: number
+ date: string
+ mcc: string
+}
+
+const auth = {
+ async is_ready(): Promise<boolean> {
+ const token = await this.get_access_token()
+ const ping = await fetch("https://developer.sparebank1.no/helloworld/ping", {
+ headers: {
+ "Authorization": "Bearer " + token
+ }
+ })
+ return ping.ok
+ },
+ async get_auth_info() {
+ const entity = await db.select({
+ refreshTokenCreated: syncSession.refreshTokenCreated,
+ accessTokenCreated: syncSession.accessTokenCreated,
+ tokens: syncSession.tokens
+ }).from(syncSession)
+ if (!entity[0]) return undefined
+ const { tokens, accessTokenCreated, refreshTokenCreated } = entity[0]
+ const tokensParsed = JSON.parse(tokens ?? "")
+ if (!tokensParsed) return undefined
+ const refreshTokenExpires = Temporal.Instant.fromEpochMilliseconds(Number(refreshTokenCreated)).add({ seconds: tokensParsed?.refresh_token_expires_in })
+ const accessTokenExpires = Temporal.Instant.fromEpochMilliseconds(Number(accessTokenCreated)).add({ seconds: tokensParsed?.expires_in })
+ return {
+ refreshTokenExpires,
+ accessTokenExpires
+ }
+ },
+ async init_auth_session(): Promise<string> {
+ const state = randomUUID()
+
+ await db.insert(syncSession).values({
+ authzState: state
+ })
+
+ const authorizeUrl = new URL("https://api.sparebank1.no/oauth/authorize");
+
+ authorizeUrl.searchParams.set("client_id", SB1_ID);
+ authorizeUrl.searchParams.set("state", state);
+ authorizeUrl.searchParams.set("redirect_uri", SB1_REDIRECT_URI);
+ authorizeUrl.searchParams.set("finInst", SB1_FIN_INST);
+ authorizeUrl.searchParams.set("response_type", "code");
+
+ return authorizeUrl.toString()
+ },
+ async get_access_token() {
+ const entity = await db.select({
+ tokens: syncSession.tokens
+ }).from(syncSession)
+ const { tokens } = entity[0]
+ if (!tokens) return null
+ const parsed = JSON.parse(tokens) as Sb1Tokens
+ return parsed.access_token as string
+ },
+ async refresh_tokem() {
+ const entity = await db.select({
+ tokens: syncSession.tokens,
+ id: syncSession.id
+ }).from(syncSession)
+
+ const { tokens, id } = entity[0]
+
+ if (!tokens) return null
+
+ const parsed = JSON.parse(tokens) as Sb1Tokens
+
+ if (!parsed.refresh_token) throw new Error("No refresh token");
+
+ const params = new URLSearchParams();
+
+ params.set("client_id", SB1_ID);
+ params.set("client_secret", SB1_SECRET);
+ params.set("refresh_token", parsed.refresh_token);
+ params.set("grant_type", "refresh_token");
+
+ const res = await fetch("https://api.sparebank1.no/oauth/token", {
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ method: "POST",
+ body: params,
+ });
+
+ const text = await res.text();
+ const epoch = Temporal.Now.instant().epochMilliseconds
+ await db.update(syncSession).set({ tokens: text, accessTokenCreated: epoch, refreshTokenCreated: epoch }).where(eq(syncSession.id, id))
+ }
+}
+
+const data = {
+ async get_accounts() {
+ const token = await auth.get_access_token()
+ if (!token) return undefined
+ const url = new URL(
+ "https://api.sparebank1.no/personal/banking/accounts",
+ );
+ const response = await fetch(url, {
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
+ })
+ if (response.ok) return await response.json() as { accounts: Array<any> }
+ else console.error(await response.text())
+ },
+ async get_transactions(accountKey: string) {
+ const token = await auth.get_access_token()
+ if (token) return undefined
+ const url = new URL(
+ "https://api.sparebank1.no/personal/banking/transactions",
+ );
+ url.searchParams.set("accountKey", accountKey);
+ const response = await fetch(url, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ return (await response.json())["transactions"] as Transaction[];
+
+ }
+}
+
+export default { auth, data } \ No newline at end of file