Files
finanzen/convex/comdirect/auth.ts
2026-06-15 11:33:23 +02:00

121 lines
3.5 KiB
TypeScript

import { action } from "../_generated/server";
import { v } from "convex/values";
import { internal } from "../_generated/api";
import { getAuthUserId } from "@convex-dev/auth/server";
import {
activateSession,
getSecondaryToken,
getSessions,
oauthToken,
validateSession,
} from "./client";
function randomUuid(): string {
return crypto.randomUUID();
}
export const start = action({
args: {
zugangsnummer: v.string(),
pin: v.string(),
},
returns: v.object({
challengeType: v.string(),
photoTanPngBase64: v.optional(v.string()),
}),
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Nicht angemeldet");
const clientId = process.env.COMDIRECT_CLIENT_ID;
const clientSecret = process.env.COMDIRECT_CLIENT_SECRET;
if (!clientId || !clientSecret) {
throw new Error("comdirect API-Zugangsdaten nicht konfiguriert");
}
const sessionUuid = randomUuid();
const tokenResponse = await oauthToken(
{
grant_type: "password",
username: args.zugangsnummer,
password: args.pin,
},
clientId,
clientSecret,
);
const sessions = await getSessions(tokenResponse.access_token, sessionUuid);
const session = sessions[0];
if (!session) throw new Error("Keine comdirect-Session gefunden");
const challenge = await validateSession(
tokenResponse.access_token,
sessionUuid,
session.identifier,
);
await ctx.runMutation(internal.comdirect.internal.upsertSession, {
userId,
sessionUuid,
identifier: session.identifier,
accessToken: tokenResponse.access_token,
refreshToken: tokenResponse.refresh_token,
secondaryActive: false,
challengeId: challenge.challengeId,
challengeType: challenge.challengeType,
status: "challenged",
expiresAt: Date.now() + tokenResponse.expires_in * 1000,
});
return {
challengeType: challenge.challengeType,
photoTanPngBase64:
challenge.challengeType === "P_TAN" ? challenge.challenge : undefined,
};
},
});
export const confirm = action({
args: { tan: v.optional(v.string()) },
returns: v.object({ success: v.boolean() }),
handler: async (ctx, args) => {
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Nicht angemeldet");
const clientId = process.env.COMDIRECT_CLIENT_ID;
const clientSecret = process.env.COMDIRECT_CLIENT_SECRET;
if (!clientId || !clientSecret) {
throw new Error("comdirect API-Zugangsdaten nicht konfiguriert");
}
const session = await ctx.runQuery(internal.comdirect.internal.getSession, { userId });
if (!session?.accessToken || !session.identifier || !session.challengeId) {
throw new Error("Keine aktive comdirect-Session");
}
await activateSession(
session.accessToken,
session.sessionUuid,
session.identifier,
session.challengeId,
args.tan,
);
const secondary = await getSecondaryToken(session.accessToken, clientId, clientSecret);
await ctx.runMutation(internal.comdirect.internal.upsertSession, {
userId,
sessionUuid: session.sessionUuid,
identifier: session.identifier,
accessToken: secondary.access_token,
refreshToken: secondary.refresh_token,
secondaryActive: true,
challengeId: session.challengeId,
challengeType: session.challengeType,
status: "active",
});
return { success: true };
},
});