Add db logic to gitea oauth flow

This commit is contained in:
CaptOrb 2025-08-28 01:00:47 +01:00
parent 9dca46ea66
commit 36fcd2b15d
8 changed files with 113 additions and 22 deletions

1
.gitignore vendored
View File

@ -144,3 +144,4 @@ dist
# Built Visual Studio Code Extensions
*.vsix
gitea/

View File

@ -2,6 +2,7 @@
import { type Request, type Response, Router } from "express";
import { config } from "../config/env";
import { createForge, listAvailableForges } from "../services/forges";
import { getOrCreateUser } from "../types/user";
const authRouter = Router();
@ -46,20 +47,20 @@ authRouter.get("/callback", async (req: Request, res: Response) => {
try {
const forge = createForge(forgeType);
const { accessToken } = await forge.exchangeCodeForToken(
code,
codeVerifier,
);
const userInfo = await forge.getUserInfo(accessToken);
const forgeUser = await forge.getUserInfo(accessToken);
// -----------------------------
// TODO: Insert user in DB
// -----------------------------
const simulatedUserId = 1; // replace with actual DB user_id
const internalUser = await getOrCreateUser(
forgeType,
forgeUser.id.toString(),
accessToken,
);
req.session.userId = simulatedUserId;
req.session.userId = internalUser.user_id;
req.session.forgeType = forgeType;
// Clear OAuth session data
@ -68,10 +69,10 @@ authRouter.get("/callback", async (req: Request, res: Response) => {
res.json({
success: true,
user: {
id: simulatedUserId, // internal molci ID
forgeUserId: userInfo.id, // forge user ID
login: userInfo.login,
avatar_url: userInfo.avatar_url,
user_id: internalUser.user_id, // internal molci ID
forgeUserId: forgeUser.id,
login: forgeUser.login,
avatar_url: forgeUser.avatar_url,
},
});
} catch (err) {

View File

@ -8,6 +8,7 @@ import { connectDB } from "./config/db";
import { config, isProduction } from "./config/env";
import authRouter from "./routes/auth";
import { createForge } from "./services/forges";
import { findUserById } from "./types/user";
const app = express();
app.use(express.json());
@ -43,11 +44,16 @@ api.register({
return res.status(401).json({ error: "Not authenticated" });
}
// TODO: Fetch user from DB here
const simulatedAccessToken = "FAKE_TOKEN"; // remove once DB is used
const user = await findUserById(userId);
if (!user) return res.status(401).json({ error: "User not found" });
const forge = createForge(forgeType);
const repos = await forge.listRepositories(simulatedAccessToken);
if (!user.access_token) {
return res.status(401).json({ error: "Missing access token" });
}
const repos = await forge.listRepositories(user.access_token);
return res.json(repos);
} catch (err) {

View File

@ -0,0 +1,5 @@
export const FORGE_IDS: Record<string, number> = {
gitea: 0,
github: 1,
gitlab: 2,
};

View File

@ -1,8 +1,8 @@
import * as arctic from "arctic";
import { config } from "../../config/env";
import type { Forge } from "../../types/forge";
import type { ForgeUser } from "../../types/forgeuser";
import type { Repository } from "../../types/openapi";
import type { User } from "../../types/user";
export class GiteaForge implements Forge {
private gitea: arctic.Gitea;
@ -45,7 +45,7 @@ export class GiteaForge implements Forge {
}
}
async getUserInfo(accessToken: string): Promise<User> {
async getUserInfo(accessToken: string): Promise<ForgeUser> {
const res = await fetch(`${config.GITEA_URL}/api/v1/user`, {
headers: { Authorization: `token ${accessToken}` },
});

View File

@ -1,5 +1,5 @@
import type { Repository } from "../types/openapi";
import type { User } from "../types/user";
import type { ForgeUser } from "./forgeuser";
export interface Forge {
getAuthorizationUrl(): { url: string; state: string; codeVerifier: string };
@ -9,7 +9,7 @@ export interface Forge {
codeVerifier: string,
): Promise<{ accessToken: string }>;
getUserInfo(accessToken: string): Promise<User>;
getUserInfo(accessToken: string): Promise<ForgeUser>;
listRepositories(accessToken: string): Promise<Repository[]>;

View File

@ -0,0 +1,5 @@
export interface ForgeUser {
id: number; // forge user ID
login: string;
avatar_url?: string;
}

View File

@ -1,8 +1,81 @@
import { pool } from "../config/db";
import { FORGE_IDS } from "../services/forges/constants";
export interface User {
id: number | string;
login: string;
email?: string;
user_id: number; // internal molci ID
avatar_url?: string;
access_token?: string;
forge_id: number;
forge_user_id: string;
forge_type: string;
token_expires_at?: Date;
}
// logic to insert user in db
const resolveForgeId = (forgeType: string): number => {
const forgeId = FORGE_IDS[forgeType];
if (forgeId === undefined)
throw new Error(`Unsupported forge type: ${forgeType}`);
return forgeId;
};
/**
* Find a user by internal Molci ID
*/
export const findUserById = async (userId: number): Promise<User | null> => {
const result = await pool.query(`SELECT * FROM users WHERE user_id = $1`, [
userId,
]);
return result.rows[0] ?? null;
};
/**
* Find a user by forge_id + forge_user_id
*/
export const findUserByForge = async (
forgeId: number,
forgeUserId: string,
): Promise<User | null> => {
const result = await pool.query(
`SELECT * FROM users WHERE forge_id = $1 AND forge_user_id = $2`,
[forgeId, forgeUserId],
);
return result.rows[0] ?? null;
};
export const insertUser = async (
forgeType: string,
forgeUserId: string,
access_token?: string,
token_expires_at?: Date,
): Promise<User> => {
const forgeId = resolveForgeId(forgeType);
const result = await pool.query(
`INSERT INTO users
(forge_id, forge_user_id, access_token, token_expires_at, forge_type)
VALUES ($1, $2, $3, $4, $5)
RETURNING *`,
[forgeId, forgeUserId, access_token, token_expires_at, forgeType],
);
return result.rows[0];
};
export const getOrCreateUser = async (
forgeType: string,
forgeUserId: string,
access_token?: string,
token_expires_at?: Date,
): Promise<User> => {
const forgeId = resolveForgeId(forgeType);
let user = await findUserByForge(forgeId, forgeUserId);
if (!user) {
user = await insertUser(
forgeType,
forgeUserId,
access_token,
token_expires_at,
);
}
return user;
};