Skip to content
Merged
Prev Previous commit
Next Next commit
Create application configuration default schemas
  • Loading branch information
daffl committed Oct 5, 2022
commit 1b654fd76899b888bfa0bd9ba373dec0e21255a9
113 changes: 2 additions & 111 deletions packages/authentication/src/options.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FromSchema } from '@feathersjs/schema'
import { FromSchema, authenticationSettingsSchema } from '@feathersjs/schema'

export const defaultOptions = {
authStrategies: [] as string[],
Expand All @@ -11,115 +11,6 @@ export const defaultOptions = {
}
}

export const authenticationSettingsSchema = {
type: 'object',
required: ['secret', 'entity', 'authStrategies'],
properties: {
secret: {
type: 'string',
description: 'The JWT signing secret'
},
entity: {
oneOf: [
{
type: 'null'
},
{
type: 'string'
}
],
description: 'The name of the authentication entity (e.g. user)'
},
entityId: {
type: 'string',
description: 'The name of the authentication entity id property'
},
service: {
type: 'string',
description: 'The path of the entity service'
},
authStrategies: {
type: 'array',
items: { type: 'string' },
description: 'A list of authentication strategy names that are allowed to create JWT access tokens'
},
parseStrategies: {
type: 'array',
items: { type: 'string' },
description:
'A list of authentication strategy names that should parse HTTP headers for authentication information (defaults to `authStrategies`)'
},
jwtOptions: {
type: 'object'
},
jwt: {
type: 'object',
properties: {
header: {
type: 'string',
default: 'Authorization',
description: 'The HTTP header containing the JWT'
},
schemes: {
type: 'array',
items: { type: 'string' },
description: 'An array of schemes to support'
}
}
},
local: {
type: 'object',
required: ['usernameField', 'passwordField'],
properties: {
usernameField: {
type: 'string',
description: 'Name of the username field (e.g. `email`)'
},
passwordField: {
type: 'string',
description: 'Name of the password field (e.g. `password`)'
},
hashSize: {
type: 'number',
description: 'The BCrypt salt length'
},
errorMessage: {
type: 'string',
default: 'Invalid login',
description: 'The error message to return on errors'
},
entityUsernameField: {
type: 'string',
description:
'Name of the username field on the entity if authentication request data and entity field names are different'
},
entityPasswordField: {
type: 'string',
description:
'Name of the password field on the entity if authentication request data and entity field names are different'
}
}
},
oauth: {
type: 'object',
properties: {
redirect: {
type: 'string'
},
origins: {
type: 'array',
items: { type: 'string' }
},
defaults: {
type: 'object',
properties: {
key: { type: 'string' },
secret: { type: 'string' }
}
}
}
}
}
} as const
export { authenticationSettingsSchema }

export type AuthenticationConfiguration = FromSchema<typeof authenticationSettingsSchema>
8 changes: 4 additions & 4 deletions packages/cli/src/app/templates/app.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import { koa, rest, bodyParser, errorHandler, parseAuthentication, cors } from '
${transports.includes('websockets') ? "import socketio from '@feathersjs/socketio'" : ''}

import type { Application } from './declarations'
import { configurationSchema } from './schemas/configuration'
import { configurationValidator } from './schemas/configuration'
import { logErrorHook } from './logger'
import { services } from './services/index'
import { channels } from './channels'

const app: Application = koa(feathers())

// Load our app configuration (see config/ folder)
app.configure(configuration(configurationSchema))
app.configure(configuration(configurationValidator))

// Set up Koa middleware
app.use(serveStatic(app.get('public')))
Expand Down Expand Up @@ -69,15 +69,15 @@ import configuration from '@feathersjs/configuration'
${transports.includes('websockets') ? "import socketio from '@feathersjs/socketio'" : ''}

import type { Application } from './declarations'
import { configurationSchema } from './schemas/configuration'
import { configurationValidator } from './schemas/configuration'
import { logger, logErrorHook } from './logger'
import { services } from './services/index'
import { channels } from './channels'

const app: Application = express(feathers())

// Load app configuration
app.configure(configuration(configurationSchema))
app.configure(configuration(configurationValidator))
app.use(cors())
app.use(compress())
app.use(json())
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/app/templates/declarations.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ const template = ({
framework
}: AppGeneratorContext) => /* ts */ `import { HookContext as FeathersHookContext, NextFunction } from '@feathersjs/feathers'
import { Application as FeathersApplication } from '@feathersjs/${framework}'
import { ConfigurationSchema } from './schemas/configuration'
import { ApplicationConfiguration } from './schemas/configuration'

export { NextFunction }

export interface Configuration extends ConfigurationSchema {}
export interface Configuration extends ApplicationConfiguration {}

// A mapping of service names to types. Will be extended in service files.
export interface ServiceTypes {}
Expand Down
77 changes: 41 additions & 36 deletions packages/cli/src/app/templates/schemas.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,53 +27,58 @@ export const dataValidator = addFormats(new Ajv({}), formats)
export const queryValidator = addFormats(new Ajv({
coerceTypes: true
}), formats)
`

const configurationJsonTemplate =
({}: AppGeneratorContext) => /* ts */ `import { defaultAppSettings, jsonSchema } from '@feathersjs/schema'
import type { FromSchema } from '@feathersjs/schema'

import { dataValidator } from './validators'

export const configurationSchema = {
type: 'object',
additionalProperties: false,
required: [ 'host', 'port', 'public' ],
properties: {
...defaultAppSettings,
host: { type: 'string' },
port: { type: 'number' },
public: { type: 'string' }
}
} as const

export const configurationValidator = jsonSchema.getValidator(configurationSchema, dataValidator)

export type ApplicationConfiguration = FromSchema<typeof configurationSchema>
`
const configurationTemplate =
({}: AppGeneratorContext) => /* ts */ `import { schema, Ajv } from '@feathersjs/schema'
import type { Infer } from '@feathersjs/schema'
import { authenticationSettingsSchema } from '@feathersjs/authentication'

const configurationTypeboxTemplate =
({}: AppGeneratorContext) => /* ts */ `import { jsonSchema } from '@feathersjs/schema'
import { Type, defaultAppConfiguration } from '@feathersjs/typebox'
import type { Static } from '@feathersjs/typebox'

import { dataValidator } from './validators'

export const configurationSchema = schema(
{
$id: 'ApplicationConfiguration',
type: 'object',
additionalProperties: false,
required: [ 'host', 'port', 'public', 'paginate' ],
properties: {
host: { type: 'string' },
port: { type: 'number' },
public: { type: 'string' },
authentication: authenticationSettingsSchema,
origins: {
type: 'array',
items: {
type: 'string'
}
},
paginate: {
type: 'object',
additionalProperties: false,
required: [ 'default', 'max' ],
properties: {
default: { type: 'number' },
max: { type: 'number' }
}
}
}
} as const,
dataValidator
)
export const configurationSchema = Type.Intersect([
defaultAppConfiguration,
Type.Object({
host: Type.String(),
port: Type.Number(),
public: Type.String()
})
])

export type ApplicationConfiguration = Static<typeof configurationSchema>

export type ConfigurationSchema = Infer<typeof configurationSchema>
export const configurationValidator = jsonSchema.getValidator(configurationSchema, dataValidator)
`

export const generate = (ctx: AppGeneratorContext) =>
generator(ctx)
.then(
renderSource(
configurationTemplate,
async (ctx) =>
ctx.schema === 'typebox' ? configurationTypeboxTemplate(ctx) : configurationJsonTemplate(ctx),
toFile<AppGeneratorContext>(({ lib }) => lib, 'schemas', 'configuration')
)
)
Expand Down
16 changes: 0 additions & 16 deletions packages/cli/src/connection/templates/knex.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,11 @@ const config = app.get('${database}')
${language === 'js' ? 'export default config' : 'module.exports = config'}
`

const configurationTemplate = ({ database }: ConnectionGeneratorContext) => `${database}: {
type: 'object',
properties: {
client: { type: 'string' },
connection: { type: 'string' }
}
},`
const importTemplate = ({ database }: ConnectionGeneratorContext) =>
`import { ${database} } from './${database}'`
const configureTemplate = ({ database }: ConnectionGeneratorContext) => `app.configure(${database})`

const toAppFile = toFile<ConnectionGeneratorContext>(({ lib }) => [lib, 'app'])
const toConfig = toFile<ConnectionGeneratorContext>(({ lib }) => [lib, 'schemas', 'configuration'])

export const generate = (ctx: ConnectionGeneratorContext) =>
generator(ctx)
Expand All @@ -67,13 +59,5 @@ export const generate = (ctx: ConnectionGeneratorContext) =>
toFile('package.json')
)
)
.then(
injectSource(
configurationTemplate,
before('authentication: authenticationSettingsSchema'),
toConfig,
false
)
)
.then(injectSource(importTemplate, before('import { services } from'), toAppFile))
.then(injectSource(configureTemplate, before('app.configure(services)'), toAppFile))
10 changes: 0 additions & 10 deletions packages/cli/src/connection/templates/mongodb.tpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ export const mongodb = (app: Application) => {
}
`

const configurationTemplate = ({ database }: ConnectionGeneratorContext) =>
` ${database}: { type: 'string' },`
const importTemplate = "import { mongodb } from './mongodb'"
const configureTemplate = 'app.configure(mongodb)'
const toAppFile = toFile<ConnectionGeneratorContext>(({ lib }) => [lib, 'app'])
Expand All @@ -36,13 +34,5 @@ export const generate = (ctx: ConnectionGeneratorContext) =>
toFile<ConnectionGeneratorContext>(({ lib }) => lib, 'mongodb')
)
)
.then(
injectSource(
configurationTemplate,
before('authentication: authenticationSettingsSchema'),
toFile<ConnectionGeneratorContext>(({ lib }) => [lib, 'schemas', 'configuration']),
false
)
)
.then(injectSource(importTemplate, before('import { services } from'), toAppFile))
.then(injectSource(configureTemplate, before('app.configure(services)'), toAppFile))
10 changes: 6 additions & 4 deletions packages/configuration/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Application, ApplicationHookContext, NextFunction } from '@feathersjs/feathers'
import { createDebug } from '@feathersjs/commons'
import { Schema } from '@feathersjs/schema'
import { Schema, Validator } from '@feathersjs/schema'
import config from 'config'

const debug = createDebug('@feathersjs/configuration')

export = function init(schema?: Schema<any>) {
export = function init(schema?: Schema<any> | Validator) {
const validator: Validator = typeof schema === 'function' ? schema : schema?.validate.bind(schema)

return (app?: Application) => {
if (!app) {
return config
Expand All @@ -21,11 +23,11 @@ export = function init(schema?: Schema<any>) {
app.set(name, value)
})

if (schema) {
if (validator) {
app.hooks({
setup: [
async (_context: ApplicationHookContext, next: NextFunction) => {
await schema.validate(configuration)
await validator(configuration)
await next()
}
]
Expand Down
Loading