@xrengine/server-core
Version:
Shared components for XREngine server
246 lines (224 loc) • 6.67 kB
text/typescript
import { Paginated, Params } from '@feathersjs/feathers'
import { SequelizeServiceOptions, Service } from 'feathers-sequelize'
import { random } from 'lodash'
import { Sequelize } from 'sequelize'
import { v1 as uuidv1 } from 'uuid'
import { isDev } from '@xrengine/common/src/config'
import { IdentityProviderInterface } from '@xrengine/common/src/dbmodels/IdentityProvider'
import { AvatarInterface } from '@xrengine/common/src/interfaces/AvatarInterface'
import { UserInterface } from '@xrengine/common/src/interfaces/User'
import { Application } from '../../../declarations'
import appConfig from '../../appconfig'
import { scopeTypeSeed } from '../../scope/scope-type/scope-type.seed'
import getFreeInviteCode from '../../util/get-free-invite-code'
import { UserParams } from '../user/user.class'
interface IdentityProviderParams extends UserParams {
bot?: boolean
}
/**
* A class for identity-provider service
*/
export class IdentityProvider<T = IdentityProviderInterface> extends Service<T> {
public app: Application
public docs: any
constructor(options: Partial<SequelizeServiceOptions>, app: Application) {
super(options)
this.app = app
}
/**
* A method used to create accessToken
*
* @param data which contains token and type
* @param params
* @returns accessToken
*/
async create(data: any, params: IdentityProviderParams = {}): Promise<T & { accessToken?: string }> {
let { token, type, password } = data
let user
let authResult
if (params.authentication) {
authResult = await (this.app.service('authentication') as any).strategies.jwt.authenticate(
{ accessToken: params.authentication.accessToken },
{}
)
if (authResult[appConfig.authentication.entity]?.userId) {
user = await this.app.service('user').get(authResult[appConfig.authentication.entity]?.userId)
}
}
if (
(!user || !user.scopes || !user.scopes.find((scope) => scope.type === 'admin:admin')) &&
params.provider &&
type !== 'password' &&
type !== 'email' &&
type !== 'sms'
)
type = 'guest' //Non-password/magiclink create requests must always be for guests
let userId = data.userId || (authResult ? authResult[appConfig.authentication.entity]?.userId : null)
let identityProvider: any
switch (type) {
case 'email':
identityProvider = {
token,
type
}
break
case 'sms':
identityProvider = {
token,
type
}
break
case 'password':
identityProvider = {
token,
password,
type
}
break
case 'github':
identityProvider = {
token: token,
type
}
break
case 'facebook':
identityProvider = {
token: token,
type
}
break
case 'google':
identityProvider = {
token: token,
type
}
break
case 'twitter':
identityProvider = {
token: token,
type
}
break
case 'linkedin':
identityProvider = {
token: token,
type
}
break
case 'discord':
identityProvider = {
token: token,
type
}
break
case 'guest':
identityProvider = {
token: token,
type: type
}
break
case 'auth0':
break
}
// if userId is not defined, then generate userId
if (!userId) {
userId = uuidv1()
}
const sequelizeClient: Sequelize = this.app.get('sequelizeClient')
const userService = this.app.service('user')
const User = sequelizeClient.model('user')
// check if there is a user with userId
let foundUser
try {
foundUser = await userService.get(userId)
} catch (err) {}
if (foundUser != null) {
// if there is the user with userId, then we add the identity provider to the user
return (await super.create(
{
...data,
...identityProvider,
userId
},
params
)) as T & { accessToken?: string }
}
// create with user association
params.sequelize = {
include: [User]
}
const code = await getFreeInviteCode(this.app)
// if there is no user with userId, then we create a user and a identity provider.
const adminCount = await this.app.service('user').Model.count({
include: [
{
model: this.app.service('scope').Model,
where: {
type: 'admin:admin'
}
}
]
})
const avatars = (await this.app
.service('avatar')
.find({ isInternal: true, query: { $limit: 1000 } })) as Paginated<AvatarInterface>
let isGuest = type === 'guest'
if (adminCount === 0) {
// in dev mode make the first guest an admin
// otherwise make the first logged in user an admin
if (isDev || !isGuest) {
type = 'admin'
}
}
let result
try {
result = await super.create(
{
...data,
...identityProvider,
user: {
id: userId,
isGuest,
inviteCode: type === 'guest' ? null : code,
avatarId: avatars.data[random(avatars.data.length - 1)].id
}
},
params
)
} catch (err) {
console.error(err)
await this.app.service('user').remove(userId)
throw err
}
// DRC
if (type === 'guest') {
if (appConfig.scopes.guest.length) {
const data = appConfig.scopes.guest.map((el) => {
return {
type: el,
userId
}
})
await this.app.service('scope').create(data)
}
result.accessToken = await this.app
.service('authentication')
.createAccessToken({}, { subject: result.id.toString() })
} else if (isDev && type === 'admin') {
// in dev mode, add all scopes to the first user made an admin
const data = scopeTypeSeed.templates.map(({ type }) => {
return { userId, type }
})
await this.app.service('scope').create(data)
result.accessToken = await this.app
.service('authentication')
.createAccessToken({}, { subject: result.id.toString() })
}
return result
}
async find(params?: UserParams): Promise<T[] | Paginated<T>> {
const loggedInUser = params!.user as UserInterface
if (params!.provider) params!.query!.userId = loggedInUser.id
return super.find(params)
}
}