Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

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