Midday / apps /api /src /rest /middleware /auth.ts
Jules
Final deployment with all fixes and verified content
c09f67c
import { verifyAccessToken } from "@api/utils/auth";
import { expandScopes } from "@api/utils/scopes";
import { isValidApiKeyFormat } from "@db/utils/api-keys";
import { apiKeyCache } from "@midday/cache/api-key-cache";
import { userCache } from "@midday/cache/user-cache";
import {
getApiKeyByToken,
getUserById,
updateApiKeyLastUsedAt,
validateAccessToken,
} from "@midday/db/queries";
import { hash } from "@midday/encryption";
import type { MiddlewareHandler } from "hono";
import { HTTPException } from "hono/http-exception";
export const withAuth: MiddlewareHandler = async (c, next) => {
const authHeader = c.req.header("Authorization");
if (!authHeader) {
throw new HTTPException(401, { message: "Authorization header required" });
}
const [scheme, token] = authHeader.split(" ");
if (scheme !== "Bearer") {
throw new HTTPException(401, { message: "Invalid authorization scheme" });
}
if (!token) {
throw new HTTPException(401, { message: "Token required" });
}
const db = c.get("db");
// Handle Supabase JWT tokens (try to verify as JWT first)
const supabaseSession = await verifyAccessToken(token);
if (supabaseSession) {
// Get user from database to get team info
const user = await getUserById(db, supabaseSession.user.id);
if (!user) {
throw new HTTPException(401, { message: "User not found" });
}
const session = {
teamId: user.teamId,
user: {
id: user.id,
email: user.email,
full_name: user.fullName,
},
};
c.set("session", session);
c.set("teamId", session.teamId);
// Grant all scopes for authenticated users via Supabase
c.set("scopes", expandScopes(["apis.all"]));
await next();
return;
}
// Handle OAuth access tokens (start with mid_access_token_)
if (token.startsWith("mid_access_token_")) {
const tokenData = await validateAccessToken(db, token);
if (!tokenData || !tokenData.user) {
throw new HTTPException(401, {
message: "Invalid or expired access token",
});
}
const session = {
teamId: tokenData.teamId,
user: {
id: tokenData.user.id,
email: tokenData.user.email,
full_name: tokenData.user.fullName,
},
oauth: {
applicationId: tokenData.applicationId,
clientId: tokenData.application?.clientId,
applicationName: tokenData.application?.name,
},
};
c.set("session", session);
c.set("teamId", session.teamId);
c.set("scopes", expandScopes(tokenData.scopes ?? []));
await next();
return;
}
// Handle API keys (start with mid_ but not mid_access_token_)
if (!token.startsWith("mid_") || !isValidApiKeyFormat(token)) {
throw new HTTPException(401, { message: "Invalid token format" });
}
const keyHash = hash(token);
// Check cache first for API key
let apiKey = await apiKeyCache.get(keyHash);
if (!apiKey) {
// If not in cache, query database
apiKey = await getApiKeyByToken(db, keyHash);
if (apiKey) {
// Store in cache for future requests
await apiKeyCache.set(keyHash, apiKey);
}
}
if (!apiKey) {
throw new HTTPException(401, { message: "Invalid API key" });
}
// Check cache first for user
let user = await userCache.get(apiKey.userId);
if (!user) {
// If not in cache, query database
user = await getUserById(db, apiKey.userId);
if (user) {
// Store in cache for future requests
await userCache.set(apiKey.userId, user);
}
}
if (!user) {
throw new HTTPException(401, { message: "User not found" });
}
const session = {
teamId: apiKey.teamId,
user: {
id: user.id,
email: user.email,
full_name: user.fullName,
},
};
c.set("session", session);
c.set("teamId", session.teamId);
c.set("scopes", expandScopes(apiKey.scopes ?? []));
// Update last used at
updateApiKeyLastUsedAt(db, apiKey.id);
await next();
};