Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Changes for client-setting, authentication-setting services hook refactor #8990

Merged
merged 11 commits into from
Oct 15, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,15 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20
Ethereal Engine. All Rights Reserved.
*/

import type { Id, Params } from '@feathersjs/feathers'
import type { KnexAdapterOptions } from '@feathersjs/knex'
import { KnexAdapter } from '@feathersjs/knex'
import * as k8s from '@kubernetes/client-node'
import type { Params } from '@feathersjs/feathers'
import { KnexAdapterParams, KnexService } from '@feathersjs/knex'

import {
AuthenticationSettingData,
AuthenticationSettingPatch,
authenticationSettingPath,
AuthenticationSettingQuery,
AuthenticationSettingType
} from '@etherealengine/engine/src/schemas/setting/authentication-setting.schema'
import { getState } from '@etherealengine/hyperflux'

import { KnexAdapterParams } from '@feathersjs/knex'
import { Application } from '../../../declarations'
import config from '../../appconfig'
import logger from '../../ServerLogger'
import { ServerState } from '../../ServerState'
import { authenticationSettingSchemaToDb } from './authentication-setting.resolvers'

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface AuthenticationSettingParams extends KnexAdapterParams<AuthenticationSettingQuery> {}
Expand All @@ -54,123 +43,9 @@ export interface AuthenticationSettingParams extends KnexAdapterParams<Authentic
export class AuthenticationSettingService<
T = AuthenticationSettingType,
ServiceParams extends Params = AuthenticationSettingParams
> extends KnexAdapter<
> extends KnexService<
AuthenticationSettingType,
AuthenticationSettingData,
AuthenticationSettingParams,
AuthenticationSettingPatch
> {
app: Application

constructor(options: KnexAdapterOptions, app: Application) {
super(options)
this.app = app
}

async find(params?: AuthenticationSettingParams) {
const auth = await super._find()
const loggedInUser = params!.user!
const data = auth.data.map((el) => {
if (!loggedInUser.scopes || !loggedInUser.scopes.find((scope) => scope.type === 'admin:admin'))
return {
id: el.id,
entity: el.entity,
service: el.service,
authStrategies: el.authStrategies,
createdAt: el.createdAt,
updatedAt: el.updatedAt
}

return {
...el,
authStrategies: el.authStrategies,
jwtOptions: el.jwtOptions,
bearerToken: el.bearerToken,
callback: el.callback,
oauth: {
...el.oauth
}
}
})
return {
total: auth.total,
limit: auth.limit,
skip: auth.skip,
data
}
}

async get(id: Id, params?: AuthenticationSettingParams) {
return super._get(id, params)
}

async patch(id: Id, data: AuthenticationSettingPatch, params?: AuthenticationSettingParams) {
const authSettings = await this.app.service(authenticationSettingPath).get(id)

if (typeof data.oauth === 'string') {
data.oauth = JSON.parse(data.oauth)
}

const newOAuth = data.oauth!
data.callback = authSettings.callback

if (typeof data.callback === 'string') {
data.callback = JSON.parse(data.callback)

// Usually above JSON.parse should be enough. But since our pre-feathers 5 data
// was serialized multiple times, therefore we need to parse it twice.
if (typeof data.callback === 'string') {
data.callback = JSON.parse(data.callback)
}
}

for (const key of Object.keys(newOAuth)) {
if (config.authentication.oauth[key]?.scope) newOAuth[key].scope = config.authentication.oauth[key].scope
if (config.authentication.oauth[key]?.custom_data)
newOAuth[key].custom_data = config.authentication.oauth[key].custom_data
if (key !== 'defaults' && data.callback && !data.callback[key])
data.callback[key] = `${config.client.url}/auth/oauth/${key}`
}

const patchResult = await super._patch(id, authenticationSettingSchemaToDb(data) as any, params)

const k8AppsClient = getState(ServerState).k8AppsClient

if (k8AppsClient) {
try {
logger.info('Attempting to refresh API pods')
const refreshApiPodResponse = await k8AppsClient.patchNamespacedDeployment(
`${config.server.releaseName}-etherealengine-api`,
'default',
{
spec: {
template: {
metadata: {
annotations: {
'kubectl.kubernetes.io/restartedAt': new Date().toISOString()
}
}
}
}
},
undefined,
undefined,
undefined,
undefined,
undefined,
{
headers: {
'Content-Type': k8s.PatchUtils.PATCH_FORMAT_STRATEGIC_MERGE_PATCH
}
}
)
logger.info(refreshApiPodResponse, 'updateBuilderTagResponse')
} catch (e) {
logger.error(e)
return e
}
}

return patchResult
}
}
> {}
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,135 @@ import { hooks as schemaHooks } from '@feathersjs/schema'
import { iff, isProvider } from 'feathers-hooks-common'

import {
AuthenticationSettingPatch,
AuthenticationSettingType,
authenticationSettingDataValidator,
authenticationSettingPatchValidator,
authenticationSettingPath,
authenticationSettingQueryValidator
} from '@etherealengine/engine/src/schemas/setting/authentication-setting.schema'
import * as k8s from '@kubernetes/client-node'

import { getState } from '@etherealengine/hyperflux'
import { BadRequest } from '@feathersjs/errors'
import { HookContext } from '../../../declarations'
import logger from '../../ServerLogger'
import { ServerState } from '../../ServerState'
import config from '../../appconfig'
import verifyScope from '../../hooks/verify-scope'
import { AuthenticationSettingService } from './authentication-setting.class'
import {
authenticationSettingDataResolver,
authenticationSettingExternalResolver,
authenticationSettingPatchResolver,
authenticationSettingQueryResolver,
authenticationSettingResolver
authenticationSettingResolver,
authenticationSettingSchemaToDb
} from './authentication-setting.resolvers'

/**
* Maps settings for admin
* @param context
* @returns
*/
const mapSettingsAdmin = async (context: HookContext<AuthenticationSettingService>) => {
const loggedInUser = context.params!.user!
if (context.result && (!loggedInUser.scopes || !loggedInUser.scopes.find((scope) => scope.type === 'admin:admin'))) {
const auth: AuthenticationSettingType[] = context.result['data'] ? context.result['data'] : context.result
const data = auth.map((el) => {
return {
id: el.id,
entity: el.entity,
service: el.service,
authStrategies: el.authStrategies,
createdAt: el.createdAt,
updatedAt: el.updatedAt,
secret: ''
}
})
context.result =
context.params.paginate === false
? data
: {
data: data,
total: data.length,
limit: context.params?.query?.$limit || 0,
skip: context.params?.query?.$skip || 0
}
}
}

/**
* Updates OAuth in data
* @param context
* @returns
*/
const ensureOAuth = async (context: HookContext<AuthenticationSettingService>) => {
if (!context.data || context.method !== 'patch') {
throw new BadRequest(`${context.path} service only works for data in ${context.method}`)
}

const data: AuthenticationSettingPatch = context.data as AuthenticationSettingPatch
const authSettings = await context.app.service(authenticationSettingPath).get(context.id!)

const newOAuth = data.oauth!
data.callback = authSettings.callback

for (const key of Object.keys(newOAuth)) {
if (config.authentication.oauth[key]?.scope) newOAuth[key].scope = config.authentication.oauth[key].scope
if (config.authentication.oauth[key]?.custom_data)
newOAuth[key].custom_data = config.authentication.oauth[key].custom_data
if (key !== 'defaults' && data.callback && !data.callback[key])
data.callback[key] = `${config.client.url}/auth/oauth/${key}`
}

context.data = authenticationSettingSchemaToDb(data) as any
}

/**
* Refreshes API pods
* @param context
* @returns
*/
const refreshAPIPods = async (context: HookContext<AuthenticationSettingService>) => {
const k8AppsClient = getState(ServerState).k8AppsClient

if (k8AppsClient) {
try {
logger.info('Attempting to refresh API pods')
const refreshApiPodResponse = await k8AppsClient.patchNamespacedDeployment(
`${config.server.releaseName}-etherealengine-api`,
'default',
{
spec: {
template: {
metadata: {
annotations: {
'kubectl.kubernetes.io/restartedAt': new Date().toISOString()
}
}
}
}
},
undefined,
undefined,
undefined,
undefined,
undefined,
{
headers: {
'Content-Type': k8s.PatchUtils.PATCH_FORMAT_STRATEGIC_MERGE_PATCH
}
}
)
logger.info(refreshApiPodResponse, 'updateBuilderTagResponse')
} catch (e) {
logger.error(e)
return e
}
}
}

export default {
around: {
all: [
Expand All @@ -65,18 +180,19 @@ export default {
patch: [
iff(isProvider('external'), verifyScope('admin', 'admin'), verifyScope('settings', 'write')),
() => schemaHooks.validateData(authenticationSettingPatchValidator),
schemaHooks.resolveData(authenticationSettingPatchResolver)
schemaHooks.resolveData(authenticationSettingPatchResolver),
ensureOAuth
],
remove: [iff(isProvider('external'), verifyScope('admin', 'admin'), verifyScope('settings', 'write'))]
},

after: {
all: [],
find: [],
find: [mapSettingsAdmin],
get: [],
create: [],
update: [],
patch: [],
patch: [refreshAPIPods],
remove: []
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export default (app: Application): void => {
multi: true
}

app.use(authenticationSettingPath, new AuthenticationSettingService(options, app), {
app.use(authenticationSettingPath, new AuthenticationSettingService(options), {
// A list of all methods this service exposes externally
methods: authenticationSettingMethods,
// You can add additional custom events to be sent to clients here
Expand Down
Loading