feat: add Google and GitHub OAuth login endpoints
This commit is contained in:
parent
1f5ae79f11
commit
09ebd18b1e
132
server.js
132
server.js
|
|
@ -782,6 +782,138 @@ function buildRouter() {
|
|||
res.json({ ok: true, userId: req.userId, authType: req.authType });
|
||||
});
|
||||
|
||||
// ── Auth: Google OAuth ───────────────────────────────────────────
|
||||
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID || '';
|
||||
const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET || '';
|
||||
const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID || '';
|
||||
const GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET || '';
|
||||
const OAUTH_BASE_URL = process.env.MAGIC_LINK_BASE_URL || 'http://localhost:3000';
|
||||
|
||||
// GET /api/auth/oauth/google — redirect to Google
|
||||
r.get('/auth/oauth/google', (req, res) => {
|
||||
const params = new URLSearchParams({
|
||||
client_id: GOOGLE_CLIENT_ID,
|
||||
redirect_uri: `${process.env.API_PUBLIC_URL || 'https://api.soar-enrich.com'}/brain/api/auth/oauth/google/callback`,
|
||||
response_type: 'code',
|
||||
scope: 'openid email profile',
|
||||
access_type: 'offline',
|
||||
prompt: 'select_account',
|
||||
});
|
||||
res.redirect(`https://accounts.google.com/o/oauth2/v2/auth?${params}`);
|
||||
});
|
||||
|
||||
// GET /api/auth/oauth/google/callback
|
||||
r.get('/auth/oauth/google/callback', async (req, res) => {
|
||||
const { code } = req.query;
|
||||
if (!code) return res.redirect(`${OAUTH_BASE_URL}/login?error=no_code`);
|
||||
try {
|
||||
// Exchange code for tokens
|
||||
const tokenRes = await fetch('https://oauth2.googleapis.com/token', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({
|
||||
code,
|
||||
client_id: GOOGLE_CLIENT_ID,
|
||||
client_secret: GOOGLE_CLIENT_SECRET,
|
||||
redirect_uri: `${process.env.API_PUBLIC_URL || 'https://api.soar-enrich.com'}/brain/api/auth/oauth/google/callback`,
|
||||
grant_type: 'authorization_code',
|
||||
}),
|
||||
});
|
||||
const tokenData = await tokenRes.json();
|
||||
if (!tokenData.access_token) throw new Error('No access token');
|
||||
|
||||
// Get user info
|
||||
const userRes = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
|
||||
headers: { Authorization: `Bearer ${tokenData.access_token}` },
|
||||
});
|
||||
const userInfo = await userRes.json();
|
||||
const email = userInfo.email?.toLowerCase();
|
||||
if (!email) throw new Error('No email from Google');
|
||||
|
||||
// Find or create user
|
||||
const existing = await pool.query(
|
||||
`SELECT id FROM users WHERE email = $1`, [email]
|
||||
);
|
||||
let userId;
|
||||
if (existing.rows.length > 0) {
|
||||
userId = existing.rows[0].id;
|
||||
} else {
|
||||
const newUser = await pool.query(
|
||||
`INSERT INTO users (email, created_at) VALUES ($1, NOW()) RETURNING id`, [email]
|
||||
);
|
||||
userId = newUser.rows[0].id;
|
||||
}
|
||||
|
||||
const token = await createSessionJWT(userId);
|
||||
res.redirect(`${OAUTH_BASE_URL}/auth/verify?token=${token}&type=oauth`);
|
||||
} catch (e) {
|
||||
console.error('[OAuth Google]', e);
|
||||
res.redirect(`${OAUTH_BASE_URL}/login?error=google_failed`);
|
||||
}
|
||||
});
|
||||
|
||||
// ── Auth: GitHub OAuth ───────────────────────────────────────────
|
||||
|
||||
// GET /api/auth/oauth/github — redirect to GitHub
|
||||
r.get('/auth/oauth/github', (req, res) => {
|
||||
const params = new URLSearchParams({
|
||||
client_id: GITHUB_CLIENT_ID,
|
||||
redirect_uri: `${process.env.API_PUBLIC_URL || 'https://api.soar-enrich.com'}/brain/api/auth/oauth/github/callback`,
|
||||
scope: 'user:email',
|
||||
});
|
||||
res.redirect(`https://github.com/login/oauth/authorize?${params}`);
|
||||
});
|
||||
|
||||
// GET /api/auth/oauth/github/callback
|
||||
r.get('/auth/oauth/github/callback', async (req, res) => {
|
||||
const { code } = req.query;
|
||||
if (!code) return res.redirect(`${OAUTH_BASE_URL}/login?error=no_code`);
|
||||
try {
|
||||
// Exchange code for token
|
||||
const tokenRes = await fetch('https://github.com/login/oauth/access_token', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||
body: JSON.stringify({
|
||||
client_id: GITHUB_CLIENT_ID,
|
||||
client_secret: GITHUB_CLIENT_SECRET,
|
||||
code,
|
||||
redirect_uri: `${process.env.API_PUBLIC_URL || 'https://api.soar-enrich.com'}/brain/api/auth/oauth/github/callback`,
|
||||
}),
|
||||
});
|
||||
const tokenData = await tokenRes.json();
|
||||
if (!tokenData.access_token) throw new Error('No access token');
|
||||
|
||||
// Get user emails
|
||||
const emailRes = await fetch('https://api.github.com/user/emails', {
|
||||
headers: { Authorization: `Bearer ${tokenData.access_token}`, 'User-Agent': 'Posimai' },
|
||||
});
|
||||
const emails = await emailRes.json();
|
||||
const primary = emails.find((e) => e.primary && e.verified);
|
||||
const email = primary?.email?.toLowerCase();
|
||||
if (!email) throw new Error('No verified email from GitHub');
|
||||
|
||||
// Find or create user
|
||||
const existing = await pool.query(
|
||||
`SELECT id FROM users WHERE email = $1`, [email]
|
||||
);
|
||||
let userId;
|
||||
if (existing.rows.length > 0) {
|
||||
userId = existing.rows[0].id;
|
||||
} else {
|
||||
const newUser = await pool.query(
|
||||
`INSERT INTO users (email, created_at) VALUES ($1, NOW()) RETURNING id`, [email]
|
||||
);
|
||||
userId = newUser.rows[0].id;
|
||||
}
|
||||
|
||||
const token = await createSessionJWT(userId);
|
||||
res.redirect(`${OAUTH_BASE_URL}/auth/verify?token=${token}&type=oauth`);
|
||||
} catch (e) {
|
||||
console.error('[OAuth GitHub]', e);
|
||||
res.redirect(`${OAUTH_BASE_URL}/login?error=github_failed`);
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/auth/session — logout (revoke session in DB)
|
||||
r.delete('/auth/session', authMiddleware, async (req, res) => {
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in New Issue