Skip to content

Commit

Permalink
feat: ms azure authentication (#49)
Browse files Browse the repository at this point in the history
* azure connect route

* msazure connect and signin
  • Loading branch information
facugon authored Apr 5, 2024
1 parent 9bf0b05 commit ae03bd4
Show file tree
Hide file tree
Showing 11 changed files with 215 additions and 11 deletions.
9 changes: 8 additions & 1 deletion server/constants/passport.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,11 @@ const PROTOCOL_LOCAL = 'local'
const PROTOCOL_OAUTH2 = 'oauth2'
const PROTOCOL_LDAP = 'ldap'

module.exports = { PROTOCOL_LOCAL, PROTOCOL_OAUTH2, PROTOCOL_LDAP }
const PROVIDER_THEEYE = 'theeye'

module.exports = {
PROVIDER_THEEYE,
PROTOCOL_LOCAL,
PROTOCOL_OAUTH2,
PROTOCOL_LDAP
}
1 change: 1 addition & 0 deletions server/models/customer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ module.exports = function (db) {
logo: { type: String, default: '' },
http_origins: { type: 'array', default: [] },
display_name: { type: String },
provider_uuid: { type: String }, // temporal
description: { type: String, default: '' },
owner_id: { type: mongoose.Schema.Types.ObjectId },
owner: { type: mongoose.Schema.Types.ObjectId, ref: 'User', default: null },
Expand Down
3 changes: 2 additions & 1 deletion server/models/session/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ module.exports = function (db) {
customer: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
customer_id: { type: mongoose.Schema.Types.ObjectId, required: true },
credential: { type: String, required: true },
protocol: { type: String, required: true }
protocol: { type: String, required: true },
provider: { type: String }
}, {
collection: 'gw_session',
discriminatorKey: '_type'
Expand Down
2 changes: 1 addition & 1 deletion server/router/auth/enterprise.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ module.exports = (app) => {
})
}

const session = await app.service.authentication.createSession({ member, protocol: passport.protocol })
const session = await app.service.authentication.createSession({ member, passport })
res.status(200).json({ access_token: session.token })
} catch (err) {
logger.error('%o', err)
Expand Down
184 changes: 184 additions & 0 deletions server/router/auth/msazure.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
const Router = require('express').Router
const ObjectId = require('mongoose').Types.ObjectId
const { ClientError, ServerError } = require('../../errors')
const logger = require('../../logger')('router:auth')

const { create } = require('../customer/common')

const AZUREAD_PROVIDER = 'azuread-openidconnect'

module.exports = (app) => {
const router = Router()

router.post('/connect',
app.service.authentication.middlewares.bearerPassport,
async (req, res, next) => {
try {
const { profile, organization } = req.body
const user = req.user

if (!organization.id) {
throw new ClientError('Tenant ID not present in payload')
}

const customer = await app.models.customer.findById(req.session.customer_id)
if (!customer) {
throw new ClientError('Invalid session', { statusCode: 401 })
}
customer.provider_uuid = `${AZUREAD_PROVIDER}:${organization.id}`
await customer.save()

const passport = await passportCreate(user, profile)

res.status(200).json('ok')
} catch (err) {
logger.error('%o', err)
next(err)
}
}
)

//
// user authentication is made using another microservice
//
router.post('/signin',
app.service.authentication.middlewares.gatewayPassport,
async (req, res, next) => {
try {
const { profile, organization } = req.body

if (!organization.id) {
throw new ClientError('Organization ID is required')
}

const customer = await app.models.customer.findOne({
provider_uuid: `${AZUREAD_PROVIDER}:${organization.id}`
})

if (!customer) {
throw new ClientError(`There is not a Customer connected to the Tenant ${organization.id}`)
}

// the customer already exists.
// here we are registering a user signin using ms azure
// we have to check whether the user is created or not
const user = await userCreate(profile)

const passport = await passportCreate(user, profile)

const member = await memberCreate(user, customer)

const session = await app.service.authentication
.createSession({ member, passport })

res.status(200).json({ access_token: session.token })
} catch (err) {
logger.error('%o', err)
next(err)
}
}
)

const userCreate = async (profile) => {

let user = await app.models.users.uiUser.findOne({
$or: [
{ email: new RegExp(profile.email, 'i') },
{ username: new RegExp(profile.username, 'i') }
]
})

if (!user) {
user = new app.models.users.uiUser()
user.username = profile.username.toLowerCase()
user.email = profile.email.toLowerCase()
user.name = profile.name
user.enabled = true
user.credential = null
user.invitation_token = null
user.devices = null
user.notifications = null
user.onboardingCompleted = true
await user.save()
return user
} else {
try {
// verify is the same username && email
if (profile.username.toLowerCase() !== user.username.toLowerCase()) {
throw new ClientError('User profile conflict. Username/Email does not match', { statusCode: 403 })
} else if (profile.email.toLowerCase() !== user.email.toLowerCase()) {
// same username, different email.
// we should check if the emails are associated to the same user.
if ( !user.extra_emails.includes(profile.email.toLowerCase()) ) {
throw new ClientError('User profile conflict. Email is already assigned', { statusCode: 403 })
}
}
} catch (err) {
await app.service.notifications.email.send({
subject: 'Enterprise Login Error',
html: `
<p>
<div>Error: ${err.message}</div>
<div>User trying to login.</div>
<ul>
<li>Email: ${profile.email}</li>
<li>Username: ${profile.username}</li>
</ul>
<div>Existent user already registered.</div>
<ul>
<li>Email: ${user.email}</li>
<li>Username: ${user.username}</li>
</ul>
</p>
`,
organization: 'Internal',
address: app.config.services.notifications.email.support
})

throw err
}
}

return user
}

const passportCreate = async (user, profile) => {
// search user provider passport.
let passport = await app.models.passport.findOne({
user_id: user._id,
provider: AZUREAD_PROVIDER
})

if (!passport) {
passport = await app.models.passport.create({
protocol: 'oauth2',
provider: AZUREAD_PROVIDER,
identifier: profile.id,
user: user._id,
user_id: user._id,
last_login: new Date()
})
}
return passport
}
const memberCreate = async (user, customer) => {
let member = await app.models.member.findOne({
user_id: user.id,
customer_id: customer._id
})

if (!member) {
member = await app.models.member.create({
user: user._id,
user_id: user.id,
customer: customer._id,
customer_id: customer._id,
customer_name: customer.name,
credential: (profile.credential || 'user')
})
}
return member
}

return router
}
2 changes: 1 addition & 1 deletion server/router/auth/social.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ module.exports = (app) => {
}

const member = memberOf[0]
const session = await app.service.authentication.createSession({ member, protocol: passport.protocol })
const session = await app.service.authentication.createSession({ member, passport })

res.cookie(
app.config.services.authentication.cookie.name || 'theeye_session',
Expand Down
2 changes: 2 additions & 0 deletions server/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const AuthRouter = require('./auth')
const AuthAdminRouter = require('./auth/admin')
const SocialAuthRouter = require('./auth/social')
const EnterpriseAuthRouter = require('./auth/enterprise')
const MSAzureAuthRouter = require('./auth/msazure')
const SessionRouter = require('./session')
const InboxRouter = require('./inbox')
const BotRouter = require('./bot')
Expand Down Expand Up @@ -44,6 +45,7 @@ class Router {
})

api.use('/api/auth', AuthRouter(app))
api.use('/api/auth/msazure', MSAzureAuthRouter(app))
api.use('/api/auth/enterprise', internalMiddleware, EnterpriseAuthRouter(app))
api.use('/api/auth/social', SocialAuthRouter(app))
api.use('/api/status', StatusRouter(app))
Expand Down
2 changes: 1 addition & 1 deletion server/router/registration/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ module.exports = (app) => {
member.user = user

// create session
const session = await app.service.authentication.createSession({ member, protocol: passport.protocol })
const session = await app.service.authentication.createSession({ member, passport })
res.json({ access_token: session.token })
} catch (err) {
next(err)
Expand Down
6 changes: 5 additions & 1 deletion server/router/session.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,11 @@ module.exports = (app) => {
}, async (req, res, next) => {
try {
const { member, session, user } = req
const newSession = await app.service.authentication.createSession({ member, protocol: session.protocol })
const passport = {
protocol: session.protocol,
provider: session.provider
}
const newSession = await app.service.authentication.createSession({ member, passport })
const model = { _id: session._id, user_id: session.user_id } // information to identify target user

app.service.notifications.sockets.sendEvent({
Expand Down
7 changes: 5 additions & 2 deletions server/router/token.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,11 @@ module.exports = (app) => {

const { member, user } = await createIntegrationToken(app, customer, data)
const tokenSession = await app.service.authentication.createSession({
member: member,
protocol: PassportConstants.PROTOCOL_LOCAL,
member,
passport: {
protocol: PassportConstants.PROTOCOL_LOCAL,
provider: PassportConstants.PROVIDER_THEEYE,
},
neverExpires: true // never expires
})

Expand Down
8 changes: 5 additions & 3 deletions server/services/authentication/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ module.exports = function (app) {
await user.save()
}

return this.createSession({ member, protocol: passport.protocol })
return this.createSession({ member, passport })
} catch (err) {
logger.error(err)
const data = err.data
Expand Down Expand Up @@ -456,7 +456,7 @@ module.exports = function (app) {
* @return {Promise} session
*/
async createSession (params) {
const { member, protocol } = params
const { member, passport } = params

let expirationDate, expirationSeconds
if (params.neverExpires === true) {
Expand Down Expand Up @@ -489,6 +489,7 @@ module.exports = function (app) {
const credential = (member.user?.credential || member.credential)

const token = app.service.authentication.issue({
issuer: passport.provider,
email: member.user.email,
username: member.user.username,
user_id: member.user._id.toString(),
Expand All @@ -508,7 +509,8 @@ module.exports = function (app) {
session.member_id = member._id
session.customer = member.customer_id
session.customer_id = member.customer_id
session.protocol = protocol
session.protocol = passport.protocol
session.provider = passport.provider
session.credential = credential

return session.save()
Expand Down

0 comments on commit ae03bd4

Please sign in to comment.