Add OAuth integration for multiple providers and implement user linking
Some checks failed
Deploy to production / deploy (push) Failing after 49s
Some checks failed
Deploy to production / deploy (push) Failing after 49s
- Created OAuth credentials setup guide for Google, Microsoft, Keycloak, ORY, and ZITADEL. - Added migration for oauth_identity table to store OAuth identities linked to users. - Implemented OAuthIdentity model for managing OAuth identities in the database. - Developed oauthService to handle OAuth login, user creation, and identity linking. - Created OAuthCallbackView and OAuthUserCallbackView components for handling OAuth responses in the frontend. - Added error handling and user feedback during the OAuth process.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import * as userService from '../services/authService.js';
|
||||
import * as oauthService from '../services/oauthService.js';
|
||||
|
||||
class AuthController {
|
||||
constructor() {
|
||||
@@ -7,6 +8,13 @@ class AuthController {
|
||||
this.forgotPassword = this.forgotPassword.bind(this);
|
||||
this.activateAccount = this.activateAccount.bind(this);
|
||||
this.logout = this.logout.bind(this);
|
||||
this.oauthProviders = this.oauthProviders.bind(this);
|
||||
this.oauthStart = this.oauthStart.bind(this);
|
||||
this.oauthExchange = this.oauthExchange.bind(this);
|
||||
this.oauthUserIdentities = this.oauthUserIdentities.bind(this);
|
||||
this.oauthUserStart = this.oauthUserStart.bind(this);
|
||||
this.oauthUserExchange = this.oauthUserExchange.bind(this);
|
||||
this.oauthUserRemove = this.oauthUserRemove.bind(this);
|
||||
}
|
||||
|
||||
async register(req, res) {
|
||||
@@ -43,6 +51,133 @@ class AuthController {
|
||||
res.status(200).json({ result: 'loggedout' });
|
||||
}
|
||||
|
||||
async oauthProviders(req, res) {
|
||||
try {
|
||||
const providers = await oauthService.getOAuthProviders();
|
||||
res.status(200).json({ providers });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async oauthStart(req, res) {
|
||||
const { provider } = req.params;
|
||||
try {
|
||||
const redirectTo = await oauthService.startOAuthLogin({ providerSlug: provider });
|
||||
res.redirect(302, redirectTo.toString());
|
||||
} catch (error) {
|
||||
const status = error.message === 'providernotconfigured' ? 503 : 500;
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async oauthExchange(req, res) {
|
||||
const { code, state } = req.body;
|
||||
try {
|
||||
const result = await oauthService.exchangeOAuthLogin({ code, state });
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
const knownErrors = new Set([
|
||||
'oauthcodemissing',
|
||||
'oauthstatemissing',
|
||||
'oauthsubjectmissing',
|
||||
'providernotconfigured',
|
||||
'oauthidentityconflict'
|
||||
]);
|
||||
const status = knownErrors.has(error.message) ? 400 : 500;
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async oauthUserIdentities(req, res) {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
try {
|
||||
const User = (await import('../models/community/user.js')).default;
|
||||
const user = await User.findOne({ where: { hashedId: hashedUserId } });
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'usernotfound' });
|
||||
}
|
||||
|
||||
const identities = await oauthService.getUserOAuthIdentities(user.id);
|
||||
res.status(200).json({ identities });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async oauthUserStart(req, res) {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const { provider } = req.params;
|
||||
try {
|
||||
const User = (await import('../models/community/user.js')).default;
|
||||
const user = await User.findOne({ where: { hashedId: hashedUserId } });
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'usernotfound' });
|
||||
}
|
||||
|
||||
const redirectTo = await oauthService.startOAuthLoginForUser({
|
||||
userId: user.id,
|
||||
providerSlug: provider
|
||||
});
|
||||
res.redirect(302, redirectTo.toString());
|
||||
} catch (error) {
|
||||
const status = error.message === 'providernotconfigured' ? 503 : 500;
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async oauthUserExchange(req, res) {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const { code, state } = req.body;
|
||||
try {
|
||||
const User = (await import('../models/community/user.js')).default;
|
||||
const user = await User.findOne({ where: { hashedId: hashedUserId } });
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'usernotfound' });
|
||||
}
|
||||
|
||||
const result = await oauthService.exchangeOAuthLoginForUser({
|
||||
userId: user.id,
|
||||
code,
|
||||
state
|
||||
});
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
const knownErrors = new Set([
|
||||
'oauthcodemissing',
|
||||
'oauthstatemissing',
|
||||
'oauthsubjectmissing',
|
||||
'providernotconfigured',
|
||||
'oauthuseridmismatch',
|
||||
'oauthidentityalreadylinked'
|
||||
]);
|
||||
const status = knownErrors.has(error.message) ? 400 : 500;
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async oauthUserRemove(req, res) {
|
||||
const { userid: hashedUserId } = req.headers;
|
||||
const { identityId } = req.params;
|
||||
try {
|
||||
const User = (await import('../models/community/user.js')).default;
|
||||
const user = await User.findOne({ where: { hashedId: hashedUserId } });
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'usernotfound' });
|
||||
}
|
||||
|
||||
const result = await oauthService.removeOAuthIdentity({
|
||||
userId: user.id,
|
||||
identityId: parseInt(identityId, 10)
|
||||
});
|
||||
res.status(200).json(result);
|
||||
} catch (error) {
|
||||
const status = error.message === 'forbidden' ? 403 : 500;
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async forgotPassword(req, res) {
|
||||
const { email } = req.body;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user