dev-prettier #4
							
								
								
									
										9
									
								
								.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | { | ||||||
|  |     "trailingComma": "es5", | ||||||
|  |     "tabWidth": 4, | ||||||
|  |     "semi": true, | ||||||
|  |     "singleQuote": true, | ||||||
|  |     "printWidth": 120, | ||||||
|  |     "quoteProps": "as-needed", | ||||||
|  |     "arrowParens": "avoid" | ||||||
|  | } | ||||||
							
								
								
									
										40
									
								
								index.ts
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								index.ts
									
									
									
									
									
								
							| @@ -1,33 +1,33 @@ | |||||||
| import { app } from "./app"; | import { app } from './app'; | ||||||
| import { logger } from "./logger"; | import { logger } from './logger'; | ||||||
|  |  | ||||||
| const port = app.get("port"); | const port = app.get('port'); | ||||||
| const host = app.get("host"); | const host = app.get('host'); | ||||||
| const server = app.listen(port); | const server = app.listen(port); | ||||||
|  |  | ||||||
| app.listen(port).then(() => { | app.listen(port).then(() => { | ||||||
|   logger.info(`Walias app listening on http://${host}:${port}`); |     logger.info(`Walias app listening on http://${host}:${port}`); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| process.on("SIGINT", () => { | process.on('SIGINT', () => { | ||||||
|   logger.info("Received SIGINT signal. Shutting down gracefully."); |     logger.info('Received SIGINT signal. Shutting down gracefully.'); | ||||||
|  |  | ||||||
|   server.close(() => { |     server.close(() => { | ||||||
|     logger.info("HTTP server closed."); |         logger.info('HTTP server closed.'); | ||||||
|     process.exit(0); |         process.exit(0); | ||||||
|   }); |     }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| process.on("SIGTERM", () => { | process.on('SIGTERM', () => { | ||||||
|   logger.info("Received SIGTERM signal. Shutting down gracefully."); |     logger.info('Received SIGTERM signal. Shutting down gracefully.'); | ||||||
|  |  | ||||||
|   server.close(() => { |     server.close(() => { | ||||||
|     logger.info("HTTP server closed."); |         logger.info('HTTP server closed.'); | ||||||
|     process.exit(0); |         process.exit(0); | ||||||
|   }); |     }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| process.on("unhandledRejection", (reason) => { | process.on('unhandledRejection', reason => { | ||||||
|   logger.error("Unhandled rejection", reason); |     logger.error('Unhandled rejection', reason); | ||||||
|   process.exit(1); |     process.exit(1); | ||||||
| }); | }); | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								logger.ts
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								logger.ts
									
									
									
									
									
								
							| @@ -1,10 +1,10 @@ | |||||||
| // For more information about this file see https://dove.feathersjs.com/guides/cli/logging.html | // For more information about this file see https://dove.feathersjs.com/guides/cli/logging.html | ||||||
| import { createLogger, format, transports } from "winston"; | import { createLogger, format, transports } from 'winston'; | ||||||
|  |  | ||||||
| // Configure the Winston logger. For the complete documentation see https://github.com/winstonjs/winston | // Configure the Winston logger. For the complete documentation see https://github.com/winstonjs/winston | ||||||
| export const logger = createLogger({ | export const logger = createLogger({ | ||||||
|   // To see more detailed errors, change this to 'debug' |     // To see more detailed errors, change this to 'debug' | ||||||
|   level: "info", |     level: 'info', | ||||||
|   format: format.combine(format.splat(), format.simple()), |     format: format.combine(format.splat(), format.simple()), | ||||||
|   transports: [new transports.Console()], |     transports: [new transports.Console()], | ||||||
| }); | }); | ||||||
|   | |||||||
							
								
								
									
										88
									
								
								src/app.ts
									
									
									
									
									
								
							
							
						
						
									
										88
									
								
								src/app.ts
									
									
									
									
									
								
							| @@ -1,25 +1,17 @@ | |||||||
| import { feathers } from "@feathersjs/feathers"; | import { feathers } from '@feathersjs/feathers'; | ||||||
| import express, { | import express, { rest, json, urlencoded, cors, serveStatic, notFound, errorHandler } from '@feathersjs/express'; | ||||||
|   rest, | import configuration from '@feathersjs/configuration'; | ||||||
|   json, | import socketio from '@feathersjs/socketio'; | ||||||
|   urlencoded, | import session from 'express-session'; | ||||||
|   cors, | import cookieParser from 'cookie-parser'; | ||||||
|   serveStatic, |  | ||||||
|   notFound, |  | ||||||
|   errorHandler, |  | ||||||
| } from "@feathersjs/express"; |  | ||||||
| import configuration from "@feathersjs/configuration"; |  | ||||||
| import socketio from "@feathersjs/socketio"; |  | ||||||
| import session from "express-session"; |  | ||||||
| import cookieParser from "cookie-parser"; |  | ||||||
|  |  | ||||||
| import type { Application } from "./declarations"; | import type { Application } from './declarations'; | ||||||
|  |  | ||||||
| import { logger } from "./logger"; | import { logger } from './logger'; | ||||||
| import { logError } from "./hooks/log-error"; | import { logError } from './hooks/log-error'; | ||||||
| import { services } from "./services/index"; | import { services } from './services/index'; | ||||||
| import { channels } from "./channels"; | import { channels } from './channels'; | ||||||
| import { randomUUID } from "crypto"; | import { randomUUID } from 'crypto'; | ||||||
|  |  | ||||||
| const app: Application = express(feathers()); | const app: Application = express(feathers()); | ||||||
|  |  | ||||||
| @@ -27,42 +19,42 @@ const app: Application = express(feathers()); | |||||||
| app.configure(configuration()); | app.configure(configuration()); | ||||||
| app.use(cors()); | app.use(cors()); | ||||||
| app.use( | app.use( | ||||||
|   json({ |     json({ | ||||||
|     limit: "20mb", |         limit: '20mb', | ||||||
|   }), |     }) | ||||||
| ); | ); | ||||||
|  |  | ||||||
| app.use(cookieParser()); | app.use(cookieParser()); | ||||||
| app.use( | app.use( | ||||||
|   session({ |     session({ | ||||||
|     secret: randomUUID(), |         secret: randomUUID(), | ||||||
|     resave: false, |         resave: false, | ||||||
|     saveUninitialized: true, |         saveUninitialized: true, | ||||||
|     cookie: { secure: false }, |         cookie: { secure: false }, | ||||||
|   }), |     }) | ||||||
| ); | ); | ||||||
|  |  | ||||||
| // Propagate session to request.params in feathers services | // Propagate session to request.params in feathers services | ||||||
| app.use(function (req, _res, next) { | app.use(function (req, _res, next) { | ||||||
|   req.feathers = { |     req.feathers = { | ||||||
|     ...req.feathers, |         ...req.feathers, | ||||||
|     session: req.session, |         session: req.session, | ||||||
|   }; |     }; | ||||||
|   next(); |     next(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| app.use(urlencoded({ extended: true })); | app.use(urlencoded({ extended: true })); | ||||||
| // Host the public folder | // Host the public folder | ||||||
| app.use("/", serveStatic(app.get("public"))); | app.use('/', serveStatic(app.get('public'))); | ||||||
|  |  | ||||||
| // Configure services and real-time functionality | // Configure services and real-time functionality | ||||||
| app.configure(rest()); | app.configure(rest()); | ||||||
| app.configure( | app.configure( | ||||||
|   socketio({ |     socketio({ | ||||||
|     cors: { |         cors: { | ||||||
|       origin: app.get("origins"), |             origin: app.get('origins'), | ||||||
|     }, |         }, | ||||||
|   }), |     }) | ||||||
| ); | ); | ||||||
| app.configure(services); | app.configure(services); | ||||||
| app.configure(channels); | app.configure(channels); | ||||||
| @@ -73,17 +65,17 @@ app.use(errorHandler({ logger })); | |||||||
|  |  | ||||||
| // Register hooks that run on all service methods | // Register hooks that run on all service methods | ||||||
| app.hooks({ | app.hooks({ | ||||||
|   around: { |     around: { | ||||||
|     all: [logError], |         all: [logError], | ||||||
|   }, |     }, | ||||||
|   before: {}, |     before: {}, | ||||||
|   after: {}, |     after: {}, | ||||||
|   error: {}, |     error: {}, | ||||||
| }); | }); | ||||||
| // Register application setup and teardown hooks here | // Register application setup and teardown hooks here | ||||||
| app.hooks({ | app.hooks({ | ||||||
|   setup: [], |     setup: [], | ||||||
|   teardown: [], |     teardown: [], | ||||||
| }); | }); | ||||||
|  |  | ||||||
| export { app }; | export { app }; | ||||||
|   | |||||||
| @@ -1,41 +1,38 @@ | |||||||
| // For more information about this file see https://dove.feathersjs.com/guides/cli/channels.html | // For more information about this file see https://dove.feathersjs.com/guides/cli/channels.html | ||||||
| import type { RealTimeConnection, Params } from "@feathersjs/feathers"; | import type { RealTimeConnection, Params } from '@feathersjs/feathers'; | ||||||
| import type { AuthenticationResult } from "@feathersjs/authentication"; | import type { AuthenticationResult } from '@feathersjs/authentication'; | ||||||
| import "@feathersjs/transport-commons"; | import '@feathersjs/transport-commons'; | ||||||
| import type { Application, HookContext } from "./declarations"; | import type { Application, HookContext } from './declarations'; | ||||||
| import { logger } from "./logger"; | import { logger } from './logger'; | ||||||
|  |  | ||||||
| export const channels = (app: Application) => { | export const channels = (app: Application) => { | ||||||
|   logger.warn( |     logger.warn( | ||||||
|     "Publishing all events to all authenticated users. See `channels.ts` and https://dove.feathersjs.com/api/channels.html for more information.", |         'Publishing all events to all authenticated users. See `channels.ts` and https://dove.feathersjs.com/api/channels.html for more information.' | ||||||
|   ); |     ); | ||||||
|  |  | ||||||
|   app.on("connection", (connection: RealTimeConnection) => { |     app.on('connection', (connection: RealTimeConnection) => { | ||||||
|     // On a new real-time connection, add it to the anonymous channel |         // On a new real-time connection, add it to the anonymous channel | ||||||
|     app.channel("anonymous").join(connection); |         app.channel('anonymous').join(connection); | ||||||
|   }); |     }); | ||||||
|  |  | ||||||
|   app.on( |     app.on('login', (authResult: AuthenticationResult, { connection }: Params) => { | ||||||
|     "login", |         // connection can be undefined if there is no | ||||||
|     (authResult: AuthenticationResult, { connection }: Params) => { |         // real-time connection, e.g. when logging in via REST | ||||||
|       // connection can be undefined if there is no |         if (connection) { | ||||||
|       // real-time connection, e.g. when logging in via REST |             // The connection is no longer anonymous, remove it | ||||||
|       if (connection) { |             app.channel('anonymous').leave(connection); | ||||||
|         // The connection is no longer anonymous, remove it |  | ||||||
|         app.channel("anonymous").leave(connection); |  | ||||||
|  |  | ||||||
|         // Add it to the authenticated user channel |             // Add it to the authenticated user channel | ||||||
|         app.channel("authenticated").join(connection); |             app.channel('authenticated').join(connection); | ||||||
|       } |         } | ||||||
|     }, |     }); | ||||||
|   ); |  | ||||||
|  |  | ||||||
|   // eslint-disable-next-line no-unused-vars |     // eslint-disable-next-line no-unused-vars | ||||||
|   app.publish((data: any, context: HookContext) => { |     app.publish((data: any, context: HookContext) => { | ||||||
|     // Here you can add event publishers to channels set up in `channels.js` |         // Here you can add event publishers to channels set up in `channels.js` | ||||||
|     // To publish only for a specific event use `app.publish(eventname, () => {})` |         // To publish only for a specific event use `app.publish(eventname, () => {})` | ||||||
|  |  | ||||||
|     // e.g. to publish all service events to all authenticated users use |         // e.g. to publish all service events to all authenticated users use | ||||||
|     return app.channel("authenticated"); |         return app.channel('authenticated'); | ||||||
|   }); |     }); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,12 +1,12 @@ | |||||||
| import axios from "axios"; | import axios from 'axios'; | ||||||
| import config from "config"; | import config from 'config'; | ||||||
|  |  | ||||||
| const wildDuckClient = axios.create({ | const wildDuckClient = axios.create({ | ||||||
|   baseURL: config.get("wildDuck.url"), |     baseURL: config.get('wildDuck.url'), | ||||||
|   headers: { |     headers: { | ||||||
|     "X-Access-Token": config.get("wildDuck.token"), |         'X-Access-Token': config.get('wildDuck.token'), | ||||||
|   }, |     }, | ||||||
|   responseType: "json", |     responseType: 'json', | ||||||
| }); | }); | ||||||
|  |  | ||||||
| export default wildDuckClient; | export default wildDuckClient; | ||||||
|   | |||||||
| @@ -1,9 +1,6 @@ | |||||||
| // For more information about this file see https://dove.feathersjs.com/guides/cli/typescript.html | // For more information about this file see https://dove.feathersjs.com/guides/cli/typescript.html | ||||||
| import { | import { HookContext as FeathersHookContext, NextFunction } from '@feathersjs/feathers'; | ||||||
|   HookContext as FeathersHookContext, | import { Application as FeathersApplication } from '@feathersjs/express'; | ||||||
|   NextFunction, |  | ||||||
| } from "@feathersjs/feathers"; |  | ||||||
| import { Application as FeathersApplication } from "@feathersjs/express"; |  | ||||||
| type ApplicationConfiguration = any; | type ApplicationConfiguration = any; | ||||||
|  |  | ||||||
| export { NextFunction }; | export { NextFunction }; | ||||||
|   | |||||||
| @@ -1,17 +1,17 @@ | |||||||
| import type { HookContext, NextFunction } from "../declarations"; | import type { HookContext, NextFunction } from '../declarations'; | ||||||
| import { logger } from "../logger"; | import { logger } from '../logger'; | ||||||
|  |  | ||||||
| export const logError = async (context: HookContext, next: NextFunction) => { | export const logError = async (context: HookContext, next: NextFunction) => { | ||||||
|   try { |     try { | ||||||
|     await next(); |         await next(); | ||||||
|   } catch (error: any) { |     } catch (error: any) { | ||||||
|     logger.error(error.stack); |         logger.error(error.stack); | ||||||
|  |  | ||||||
|     // Log validation errors |         // Log validation errors | ||||||
|     if (error.data) { |         if (error.data) { | ||||||
|       logger.error("Data: %O", error.data); |             logger.error('Data: %O', error.data); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         throw error; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     throw error; |  | ||||||
|   } |  | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| import { NotAuthenticated } from "@feathersjs/errors"; | import { NotAuthenticated } from '@feathersjs/errors'; | ||||||
| import type { HookContext, NextFunction } from "../declarations"; | import type { HookContext, NextFunction } from '../declarations'; | ||||||
|  |  | ||||||
| // Check if user is stored in session | // Check if user is stored in session | ||||||
| export const validateAuth = async (context: HookContext) => { | export const validateAuth = async (context: HookContext) => { | ||||||
|   if (!context.params.session?.user) { |     if (!context.params.session?.user) { | ||||||
|     throw new NotAuthenticated("Not authenticated"); |         throw new NotAuthenticated('Not authenticated'); | ||||||
|   } |     } | ||||||
| }; | }; | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								src/index.ts
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								src/index.ts
									
									
									
									
									
								
							| @@ -1,13 +1,11 @@ | |||||||
| import { app } from "./app"; | import { app } from './app'; | ||||||
| import { logger } from "./logger"; | import { logger } from './logger'; | ||||||
|  |  | ||||||
| const port = app.get("port"); | const port = app.get('port'); | ||||||
| const host = app.get("host"); | const host = app.get('host'); | ||||||
|  |  | ||||||
| process.on("unhandledRejection", (reason) => | process.on('unhandledRejection', reason => logger.error('Unhandled Rejection %O', reason)); | ||||||
|   logger.error("Unhandled Rejection %O", reason), |  | ||||||
| ); |  | ||||||
|  |  | ||||||
| app.listen(port).then(() => { | app.listen(port).then(() => { | ||||||
|   logger.info(`Feathers app listening on http://${host}:${port}`); |     logger.info(`Feathers app listening on http://${host}:${port}`); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| // For more information about this file see https://dove.feathersjs.com/guides/cli/logging.html | // For more information about this file see https://dove.feathersjs.com/guides/cli/logging.html | ||||||
| import { createLogger, format, transports } from "winston"; | import { createLogger, format, transports } from 'winston'; | ||||||
|  |  | ||||||
| // Configure the Winston logger. For the complete documentation see https://github.com/winstonjs/winston | // Configure the Winston logger. For the complete documentation see https://github.com/winstonjs/winston | ||||||
| export const logger = createLogger({ | export const logger = createLogger({ | ||||||
|   // To see more detailed errors, change this to 'debug' |     // To see more detailed errors, change this to 'debug' | ||||||
|   level: "info", |     level: 'info', | ||||||
|   format: format.combine(format.splat(), format.simple()), |     format: format.combine(format.splat(), format.simple()), | ||||||
|   transports: [new transports.Console()], |     transports: [new transports.Console()], | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,33 +1,29 @@ | |||||||
| import type { | import type { NullableId, Params, ServiceInterface } from '@feathersjs/feathers'; | ||||||
|   NullableId, |  | ||||||
|   Params, |  | ||||||
|   ServiceInterface, |  | ||||||
| } from "@feathersjs/feathers"; |  | ||||||
|  |  | ||||||
| import type { Application } from "../../declarations"; | import type { Application } from '../../declarations'; | ||||||
| import wildDuckClient from "../../clients/wildduck.client"; | import wildDuckClient from '../../clients/wildduck.client'; | ||||||
| import { faker } from "@faker-js/faker"; | import { faker } from '@faker-js/faker'; | ||||||
| import { BadRequest } from "@feathersjs/errors"; | import { BadRequest } from '@feathersjs/errors'; | ||||||
| import config from "config"; | import config from 'config'; | ||||||
|  |  | ||||||
| interface Alias { | interface Alias { | ||||||
|   success: boolean; |     success: boolean; | ||||||
|   id: string; |     id: string; | ||||||
|   address: string; |     address: string; | ||||||
|   main: boolean; |     main: boolean; | ||||||
|   user: string; |     user: string; | ||||||
|   tags: string[]; |     tags: string[]; | ||||||
|   created: string; |     created: string; | ||||||
| } | } | ||||||
|  |  | ||||||
| interface GetAddressInfoResponse { | interface GetAddressInfoResponse { | ||||||
|   success: boolean; |     success: boolean; | ||||||
|   results: Alias[]; |     results: Alias[]; | ||||||
| } | } | ||||||
|  |  | ||||||
| interface CreateAddressResponse { | interface CreateAddressResponse { | ||||||
|   success: boolean; |     success: boolean; | ||||||
|   id: string; |     id: string; | ||||||
| } | } | ||||||
|  |  | ||||||
| type AliasesData = any; | type AliasesData = any; | ||||||
| @@ -37,102 +33,85 @@ type AliasesQuery = any; | |||||||
| export type { Alias as Aliases, AliasesData, AliasesPatch, AliasesQuery }; | export type { Alias as Aliases, AliasesData, AliasesPatch, AliasesQuery }; | ||||||
|  |  | ||||||
| export interface AliasesServiceOptions { | export interface AliasesServiceOptions { | ||||||
|   app: Application; |     app: Application; | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface AliasesParams extends Params<AliasesQuery> { | export interface AliasesParams extends Params<AliasesQuery> { | ||||||
|   session?: any; |     session?: any; | ||||||
| } | } | ||||||
|  |  | ||||||
| export class AliasesService<ServiceParams extends AliasesParams = AliasesParams> | export class AliasesService<ServiceParams extends AliasesParams = AliasesParams> | ||||||
|   implements ServiceInterface<Alias, AliasesData, ServiceParams, AliasesPatch> |     implements ServiceInterface<Alias, AliasesData, ServiceParams, AliasesPatch> | ||||||
| { | { | ||||||
|   constructor(public options: AliasesServiceOptions) {} |     constructor(public options: AliasesServiceOptions) {} | ||||||
|  |  | ||||||
|   async find(params: ServiceParams): Promise<Alias[]> { |     async find(params: ServiceParams): Promise<Alias[]> { | ||||||
|     const userId = await this.getUserIdByEmailAddress(params); |         const userId = await this.getUserIdByEmailAddress(params); | ||||||
|  |  | ||||||
|     return this.getUserAddresses(userId); |         return this.getUserAddresses(userId); | ||||||
|   } |  | ||||||
|  |  | ||||||
|   async create(data: AliasesData, params: ServiceParams): Promise<Alias>; |  | ||||||
|   async create( |  | ||||||
|     data: AliasesData, |  | ||||||
|     params: ServiceParams, |  | ||||||
|   ): Promise<Alias | Alias[]> { |  | ||||||
|     const userId = await this.getUserIdByEmailAddress(params); |  | ||||||
|  |  | ||||||
|     const randomString = faker.git.commitSha({ length: 4 }); |  | ||||||
|  |  | ||||||
|     // Replace all non-alphanumeric characters with nothing and spaces with dashes |  | ||||||
|     const alias = |  | ||||||
|       `${faker.color.human()}-${faker.animal.snake()}-${randomString}` |  | ||||||
|         .replace(/\s+/g, "-") |  | ||||||
|         .replace(/[^a-zA-Z0-9-]/g, "") |  | ||||||
|         .toLowerCase(); |  | ||||||
|  |  | ||||||
|     const emailDomain = config.get("wildDuck.domain"); |  | ||||||
|  |  | ||||||
|     const createResult = await wildDuckClient.post<CreateAddressResponse>( |  | ||||||
|       `/users/${userId}/addresses`, |  | ||||||
|       { |  | ||||||
|         address: `${alias}@${emailDomain}`, |  | ||||||
|       }, |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     if (!createResult.data.success) { |  | ||||||
|       throw new BadRequest("Failed to create alias"); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return this.getUserAddresses(userId); |     async create(data: AliasesData, params: ServiceParams): Promise<Alias>; | ||||||
|   } |     async create(data: AliasesData, params: ServiceParams): Promise<Alias | Alias[]> { | ||||||
|  |         const userId = await this.getUserIdByEmailAddress(params); | ||||||
|  |  | ||||||
|   private async getUserIdByEmailAddress( |         const randomString = faker.git.commitSha({ length: 4 }); | ||||||
|     params: ServiceParams, |  | ||||||
|   ): Promise<string> { |  | ||||||
|     const emails = params.session?.user?.emails; |  | ||||||
|  |  | ||||||
|     const addressInfoResponse = await Promise.any( |         // Replace all non-alphanumeric characters with nothing and spaces with dashes | ||||||
|       emails |         const alias = `${faker.color.human()}-${faker.animal.snake()}-${randomString}` | ||||||
|       .filter((email: string) => email.endsWith(config.get("wildDuck.preferredDomain"))) |             .replace(/\s+/g, '-') | ||||||
|       .map((email: string) => |             .replace(/[^a-zA-Z0-9-]/g, '') | ||||||
|         wildDuckClient.get<Alias>(`addresses/resolve/${email}`), |             .toLowerCase(); | ||||||
|       ), |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     return addressInfoResponse.data.user; |         const emailDomain = config.get('wildDuck.domain'); | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private async getUserAddresses(userId: string): Promise<Alias[]> { |         const createResult = await wildDuckClient.post<CreateAddressResponse>(`/users/${userId}/addresses`, { | ||||||
|     const { data: userAddressesResponse } = |             address: `${alias}@${emailDomain}`, | ||||||
|       await wildDuckClient.get<GetAddressInfoResponse>( |         }); | ||||||
|         `/users/${userId}/addresses`, |  | ||||||
|       ); |  | ||||||
|  |  | ||||||
|     return userAddressesResponse.results; |         if (!createResult.data.success) { | ||||||
|   } |             throw new BadRequest('Failed to create alias'); | ||||||
|  |         } | ||||||
|  |  | ||||||
|   async remove(id: NullableId, params: ServiceParams): Promise<Alias[]> { |         return this.getUserAddresses(userId); | ||||||
|     const { data: addressInfoResponse } = await wildDuckClient.get<Alias>( |  | ||||||
|       `addresses/resolve/${id}`, |  | ||||||
|     ); |  | ||||||
|     const allowedDomain: string = config.get("wildDuck.domain"); |  | ||||||
|  |  | ||||||
|     // If address does not match the allowed domain, throw an error |  | ||||||
|     if ( |  | ||||||
|       !allowedDomain || |  | ||||||
|       !addressInfoResponse.address.endsWith(allowedDomain) |  | ||||||
|     ) { |  | ||||||
|       throw new BadRequest("Unable to delete address"); |  | ||||||
|     } |     } | ||||||
|     const userId = await this.getUserIdByEmailAddress(params); |  | ||||||
|  |  | ||||||
|     await wildDuckClient.delete<Alias>(`users/${userId}/addresses/${id}`); |     private async getUserIdByEmailAddress(params: ServiceParams): Promise<string> { | ||||||
|  |         const emails = params.session?.user?.emails; | ||||||
|  |  | ||||||
|     return this.getUserAddresses(userId); |         const addressInfoResponse = await Promise.any( | ||||||
|   } |             emails | ||||||
|  |                 .filter((email: string) => email.endsWith(config.get('wildDuck.preferredDomain'))) | ||||||
|  |                 .map((email: string) => wildDuckClient.get<Alias>(`addresses/resolve/${email}`)) | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         return addressInfoResponse.data.user; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private async getUserAddresses(userId: string): Promise<Alias[]> { | ||||||
|  |         const { data: userAddressesResponse } = await wildDuckClient.get<GetAddressInfoResponse>( | ||||||
|  |             `/users/${userId}/addresses` | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         return userAddressesResponse.results; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async remove(id: NullableId, params: ServiceParams): Promise<Alias[]> { | ||||||
|  |         const { data: addressInfoResponse } = await wildDuckClient.get<Alias>(`addresses/resolve/${id}`); | ||||||
|  |         const allowedDomain: string = config.get('wildDuck.domain'); | ||||||
|  |  | ||||||
|  |         // If address does not match the allowed domain, throw an error | ||||||
|  |         if (!allowedDomain || !addressInfoResponse.address.endsWith(allowedDomain)) { | ||||||
|  |             throw new BadRequest('Unable to delete address'); | ||||||
|  |         } | ||||||
|  |         const userId = await this.getUserIdByEmailAddress(params); | ||||||
|  |  | ||||||
|  |         await wildDuckClient.delete<Alias>(`users/${userId}/addresses/${id}`); | ||||||
|  |  | ||||||
|  |         return this.getUserAddresses(userId); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| export const getOptions = (app: Application) => { | export const getOptions = (app: Application) => { | ||||||
|   return { app }; |     return { app }; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,39 +1,39 @@ | |||||||
| import type { Application } from "../../declarations"; | import type { Application } from '../../declarations'; | ||||||
| import { validateAuth } from "../../hooks/validate-auth"; | import { validateAuth } from '../../hooks/validate-auth'; | ||||||
| import { AliasesService, getOptions } from "./aliases.class"; | import { AliasesService, getOptions } from './aliases.class'; | ||||||
|  |  | ||||||
| export const aliasesPath = "aliases"; | export const aliasesPath = 'aliases'; | ||||||
| export const aliasesMethods = ["find", "create", "remove"] as const; | export const aliasesMethods = ['find', 'create', 'remove'] as const; | ||||||
|  |  | ||||||
| export * from "./aliases.class"; | export * from './aliases.class'; | ||||||
|  |  | ||||||
| export const aliases = (app: Application) => { | export const aliases = (app: Application) => { | ||||||
|   app.use(aliasesPath, new AliasesService(getOptions(app)), { |     app.use(aliasesPath, new AliasesService(getOptions(app)), { | ||||||
|     methods: aliasesMethods, |         methods: aliasesMethods, | ||||||
|     events: [], |         events: [], | ||||||
|   }); |     }); | ||||||
|  |  | ||||||
|   app.service(aliasesPath).hooks({ |     app.service(aliasesPath).hooks({ | ||||||
|     around: { |         around: { | ||||||
|       all: [], |             all: [], | ||||||
|     }, |         }, | ||||||
|     before: { |         before: { | ||||||
|       all: [validateAuth], |             all: [validateAuth], | ||||||
|       find: [], |             find: [], | ||||||
|       create: [], |             create: [], | ||||||
|     }, |         }, | ||||||
|     after: { |         after: { | ||||||
|       all: [], |             all: [], | ||||||
|     }, |         }, | ||||||
|     error: { |         error: { | ||||||
|       all: [], |             all: [], | ||||||
|     }, |         }, | ||||||
|   }); |     }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // Add this service to the service type index | // Add this service to the service type index | ||||||
| declare module "../../declarations" { | declare module '../../declarations' { | ||||||
|   interface ServiceTypes { |     interface ServiceTypes { | ||||||
|     [aliasesPath]: AliasesService; |         [aliasesPath]: AliasesService; | ||||||
|   } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,9 +1,9 @@ | |||||||
| import type { Params, ServiceInterface } from "@feathersjs/feathers"; | import type { Params, ServiceInterface } from '@feathersjs/feathers'; | ||||||
|  |  | ||||||
| import type { Application } from "../../declarations"; | import type { Application } from '../../declarations'; | ||||||
|  |  | ||||||
| import { Issuer, generators } from "openid-client"; | import { Issuer, generators } from 'openid-client'; | ||||||
| import config from "config"; | import config from 'config'; | ||||||
|  |  | ||||||
| type AuthOidcResponse = string; | type AuthOidcResponse = string; | ||||||
| type AuthOidcQuery = any; | type AuthOidcQuery = any; | ||||||
| @@ -11,43 +11,42 @@ type AuthOidcQuery = any; | |||||||
| export type { AuthOidcResponse as AuthOidc, AuthOidcQuery }; | export type { AuthOidcResponse as AuthOidc, AuthOidcQuery }; | ||||||
|  |  | ||||||
| export interface AuthOidcServiceOptions { | export interface AuthOidcServiceOptions { | ||||||
|   app: Application; |     app: Application; | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface AuthOidcParams extends Params<AuthOidcQuery> { | export interface AuthOidcParams extends Params<AuthOidcQuery> { | ||||||
|   session?: any; |     session?: any; | ||||||
| } | } | ||||||
|  |  | ||||||
| export class AuthOidcService< | export class AuthOidcService<ServiceParams extends AuthOidcParams = AuthOidcParams> | ||||||
|   ServiceParams extends AuthOidcParams = AuthOidcParams, |     implements ServiceInterface<AuthOidcResponse, ServiceParams> | ||||||
| > implements ServiceInterface<AuthOidcResponse, ServiceParams> |  | ||||||
| { | { | ||||||
|   constructor(public options: AuthOidcServiceOptions) {} |     constructor(public options: AuthOidcServiceOptions) {} | ||||||
|  |  | ||||||
|   async find(params: ServiceParams): Promise<AuthOidcResponse> { |     async find(params: ServiceParams): Promise<AuthOidcResponse> { | ||||||
|     const issuer = await Issuer.discover(config.get("oidc.gatewayUri")); |         const issuer = await Issuer.discover(config.get('oidc.gatewayUri')); | ||||||
|     const client = new issuer.Client({ |         const client = new issuer.Client({ | ||||||
|       client_id: config.get("oidc.clientId"), |             client_id: config.get('oidc.clientId'), | ||||||
|       client_secret: config.get("oidc.clientSecret"), |             client_secret: config.get('oidc.clientSecret'), | ||||||
|       redirect_uris: [config.get("oidc.redirectUris")], |             redirect_uris: [config.get('oidc.redirectUris')], | ||||||
|       response_types: ["code"], |             response_types: ['code'], | ||||||
|     }); |         }); | ||||||
|     const codeVerifier = generators.codeVerifier(); |         const codeVerifier = generators.codeVerifier(); | ||||||
|     const codeChallenge = generators.codeChallenge(codeVerifier); |         const codeChallenge = generators.codeChallenge(codeVerifier); | ||||||
|  |  | ||||||
|     const url = client.authorizationUrl({ |         const url = client.authorizationUrl({ | ||||||
|       redirect_uri: config.get("clientUrl") + "/auth-oidc/callback", |             redirect_uri: config.get('clientUrl') + '/auth-oidc/callback', | ||||||
|       scope: "openid profile offline_access", |             scope: 'openid profile offline_access', | ||||||
|       response_type: "code", |             response_type: 'code', | ||||||
|       code_challenge: codeChallenge, |             code_challenge: codeChallenge, | ||||||
|       code_challenge_method: "S256", |             code_challenge_method: 'S256', | ||||||
|     }); |         }); | ||||||
|  |  | ||||||
|     params.session.codeVerifier = codeVerifier; |         params.session.codeVerifier = codeVerifier; | ||||||
|     return url; |         return url; | ||||||
|   } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| export const getOptions = (app: Application) => { | export const getOptions = (app: Application) => { | ||||||
|   return { app }; |     return { app }; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,45 +1,45 @@ | |||||||
| import type { Application } from "../../declarations"; | import type { Application } from '../../declarations'; | ||||||
| import { AuthOidcService, getOptions } from "./auth-oidc.class"; | import { AuthOidcService, getOptions } from './auth-oidc.class'; | ||||||
|  |  | ||||||
| export const authOidcPath = "auth-oidc"; | export const authOidcPath = 'auth-oidc'; | ||||||
| export const authOidcMethods = ["find"] as const; | export const authOidcMethods = ['find'] as const; | ||||||
|  |  | ||||||
| export * from "./auth-oidc.class"; | export * from './auth-oidc.class'; | ||||||
|  |  | ||||||
| export const authOidc = (app: Application) => { | export const authOidc = (app: Application) => { | ||||||
|   // TODO: fix this to use the correct type |     // TODO: fix this to use the correct type | ||||||
|   // @ts-ignore |     // @ts-ignore | ||||||
|   app.use( |     app.use( | ||||||
|     authOidcPath, |         authOidcPath, | ||||||
|     new AuthOidcService(getOptions(app)), |         new AuthOidcService(getOptions(app)), | ||||||
|     { |         { | ||||||
|       methods: authOidcMethods, |             methods: authOidcMethods, | ||||||
|       events: [], |             events: [], | ||||||
|     }, |         }, | ||||||
|     (req: any, res: any) => { |         (req: any, res: any) => { | ||||||
|       return res.redirect(res.data); |             return res.redirect(res.data); | ||||||
|     }, |         } | ||||||
|   ); |     ); | ||||||
|  |  | ||||||
|   app.service(authOidcPath).hooks({ |     app.service(authOidcPath).hooks({ | ||||||
|     around: { |         around: { | ||||||
|       all: [], |             all: [], | ||||||
|     }, |         }, | ||||||
|     before: { |         before: { | ||||||
|       all: [], |             all: [], | ||||||
|       find: [], |             find: [], | ||||||
|     }, |         }, | ||||||
|     after: { |         after: { | ||||||
|       all: [], |             all: [], | ||||||
|     }, |         }, | ||||||
|     error: { |         error: { | ||||||
|       all: [], |             all: [], | ||||||
|     }, |         }, | ||||||
|   }); |     }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| declare module "../../declarations" { | declare module '../../declarations' { | ||||||
|   interface ServiceTypes { |     interface ServiceTypes { | ||||||
|     [authOidcPath]: AuthOidcService; |         [authOidcPath]: AuthOidcService; | ||||||
|   } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,68 +1,56 @@ | |||||||
| import type { Params, ServiceInterface } from "@feathersjs/feathers"; | import type { Params, ServiceInterface } from '@feathersjs/feathers'; | ||||||
| import type { Application } from "../../../declarations"; | import type { Application } from '../../../declarations'; | ||||||
| import { Issuer } from "openid-client"; | import { Issuer } from 'openid-client'; | ||||||
|  |  | ||||||
| import config from "config"; | import config from 'config'; | ||||||
|  |  | ||||||
| type AuthOidcCallback = string; | type AuthOidcCallback = string; | ||||||
| type AuthOidcCallbackData = any; | type AuthOidcCallbackData = any; | ||||||
| type AuthOidcCallbackPatch = any; | type AuthOidcCallbackPatch = any; | ||||||
| type AuthOidcCallbackQuery = any; | type AuthOidcCallbackQuery = any; | ||||||
|  |  | ||||||
| export type { | export type { AuthOidcCallback, AuthOidcCallbackData, AuthOidcCallbackPatch, AuthOidcCallbackQuery }; | ||||||
|   AuthOidcCallback, |  | ||||||
|   AuthOidcCallbackData, |  | ||||||
|   AuthOidcCallbackPatch, |  | ||||||
|   AuthOidcCallbackQuery, |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export interface AuthOidcCallbackServiceOptions { | export interface AuthOidcCallbackServiceOptions { | ||||||
|   app: Application; |     app: Application; | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface AuthOidcCallbackParams extends Params<AuthOidcCallbackQuery> { | export interface AuthOidcCallbackParams extends Params<AuthOidcCallbackQuery> { | ||||||
|   session?: any; |     session?: any; | ||||||
|   query: { |     query: { | ||||||
|     iss: string; |         iss: string; | ||||||
|     code: string; |         code: string; | ||||||
|   }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| export class AuthOidcCallbackService< | export class AuthOidcCallbackService<ServiceParams extends AuthOidcCallbackParams = AuthOidcCallbackParams> | ||||||
|   ServiceParams extends AuthOidcCallbackParams = AuthOidcCallbackParams, |     implements ServiceInterface<AuthOidcCallback, AuthOidcCallbackData, ServiceParams, AuthOidcCallbackPatch> | ||||||
| > implements |  | ||||||
|     ServiceInterface< |  | ||||||
|       AuthOidcCallback, |  | ||||||
|       AuthOidcCallbackData, |  | ||||||
|       ServiceParams, |  | ||||||
|       AuthOidcCallbackPatch |  | ||||||
|     > |  | ||||||
| { | { | ||||||
|   constructor(public options: AuthOidcCallbackServiceOptions) {} |     constructor(public options: AuthOidcCallbackServiceOptions) {} | ||||||
|  |  | ||||||
|   async find(params: ServiceParams): Promise<AuthOidcCallback> { |     async find(params: ServiceParams): Promise<AuthOidcCallback> { | ||||||
|     const issuer = await Issuer.discover(config.get("oidc.gatewayUri")); |         const issuer = await Issuer.discover(config.get('oidc.gatewayUri')); | ||||||
|     const client = new issuer.Client({ |         const client = new issuer.Client({ | ||||||
|       client_id: config.get("oidc.clientId"), |             client_id: config.get('oidc.clientId'), | ||||||
|       client_secret: config.get("oidc.clientSecret"), |             client_secret: config.get('oidc.clientSecret'), | ||||||
|       redirect_uris: [config.get("oidc.redirectUris")], |             redirect_uris: [config.get('oidc.redirectUris')], | ||||||
|       response_types: ["code"], |             response_types: ['code'], | ||||||
|     }); |         }); | ||||||
|  |  | ||||||
|     const codeVerifier = params.session.codeVerifier; |         const codeVerifier = params.session.codeVerifier; | ||||||
|     const tokenSet = await client.callback( |         const tokenSet = await client.callback( | ||||||
|       config.get("clientUrl") + "/auth-oidc/callback", |             config.get('clientUrl') + '/auth-oidc/callback', | ||||||
|       { code: params.query.code, iss: params.query.iss }, |             { code: params.query.code, iss: params.query.iss }, | ||||||
|       { code_verifier: codeVerifier }, |             { code_verifier: codeVerifier } | ||||||
|     ); |         ); | ||||||
|     const userinfo = await client.userinfo(tokenSet.access_token as string); |         const userinfo = await client.userinfo(tokenSet.access_token as string); | ||||||
|  |  | ||||||
|     params.session.user = userinfo; |         params.session.user = userinfo; | ||||||
|  |  | ||||||
|     return "/"; |         return '/'; | ||||||
|   } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| export const getOptions = (app: Application) => { | export const getOptions = (app: Application) => { | ||||||
|   return { app }; |     return { app }; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,49 +1,46 @@ | |||||||
| import { http } from "@feathersjs/transport-commons"; | import { http } from '@feathersjs/transport-commons'; | ||||||
| import type { Application } from "../../../declarations"; | import type { Application } from '../../../declarations'; | ||||||
| import { | import { AuthOidcCallbackService, getOptions } from './auth-oidc-callback.class'; | ||||||
|   AuthOidcCallbackService, |  | ||||||
|   getOptions, |  | ||||||
| } from "./auth-oidc-callback.class"; |  | ||||||
|  |  | ||||||
| export const authOidcCallbackPath = "auth-oidc/callback"; | export const authOidcCallbackPath = 'auth-oidc/callback'; | ||||||
| export const authOidcCallbackMethods = ["find"] as const; | export const authOidcCallbackMethods = ['find'] as const; | ||||||
|  |  | ||||||
| export * from "./auth-oidc-callback.class"; | export * from './auth-oidc-callback.class'; | ||||||
|  |  | ||||||
| export const authOidcCallback = (app: Application) => { | export const authOidcCallback = (app: Application) => { | ||||||
|   // TODO: fix this to use the correct type |     // TODO: fix this to use the correct type | ||||||
|   // @ts-ignore |     // @ts-ignore | ||||||
|   app.use( |     app.use( | ||||||
|     authOidcCallbackPath, |         authOidcCallbackPath, | ||||||
|     new AuthOidcCallbackService(getOptions(app)), |         new AuthOidcCallbackService(getOptions(app)), | ||||||
|     { |         { | ||||||
|       methods: authOidcCallbackMethods, |             methods: authOidcCallbackMethods, | ||||||
|       events: [], |             events: [], | ||||||
|     }, |         }, | ||||||
|     (req: any, res: any) => { |         (req: any, res: any) => { | ||||||
|       return res.redirect(res.data); |             return res.redirect(res.data); | ||||||
|     }, |         } | ||||||
|   ); |     ); | ||||||
|  |  | ||||||
|   app.service(authOidcCallbackPath).hooks({ |     app.service(authOidcCallbackPath).hooks({ | ||||||
|     around: { |         around: { | ||||||
|       all: [], |             all: [], | ||||||
|     }, |         }, | ||||||
|     before: { |         before: { | ||||||
|       all: [], |             all: [], | ||||||
|       find: [], |             find: [], | ||||||
|     }, |         }, | ||||||
|     after: { |         after: { | ||||||
|       all: [], |             all: [], | ||||||
|     }, |         }, | ||||||
|     error: { |         error: { | ||||||
|       all: [], |             all: [], | ||||||
|     }, |         }, | ||||||
|   }); |     }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| declare module "../../../declarations" { | declare module '../../../declarations' { | ||||||
|   interface ServiceTypes { |     interface ServiceTypes { | ||||||
|     [authOidcCallbackPath]: AuthOidcCallbackService; |         [authOidcCallbackPath]: AuthOidcCallbackService; | ||||||
|   } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| import { authOidcCallback } from "./auth-oidc/callback/auth-oidc-callback"; | import { authOidcCallback } from './auth-oidc/callback/auth-oidc-callback'; | ||||||
| import { authOidc } from "./auth-oidc/auth-oidc"; | import { authOidc } from './auth-oidc/auth-oidc'; | ||||||
| import { aliases } from "./aliases/aliases"; | import { aliases } from './aliases/aliases'; | ||||||
| import type { Application } from "../declarations"; | import type { Application } from '../declarations'; | ||||||
|  |  | ||||||
| export const services = (app: Application) => { | export const services = (app: Application) => { | ||||||
|   app.configure(authOidcCallback); |     app.configure(authOidcCallback); | ||||||
|   app.configure(authOidc); |     app.configure(authOidc); | ||||||
|   app.configure(aliases); |     app.configure(aliases); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,29 +1,29 @@ | |||||||
| // For more information about this file see https://dove.feathersjs.com/guides/cli/validators.html | // For more information about this file see https://dove.feathersjs.com/guides/cli/validators.html | ||||||
| import { Ajv, addFormats } from "@feathersjs/schema"; | import { Ajv, addFormats } from '@feathersjs/schema'; | ||||||
| import type { FormatsPluginOptions } from "@feathersjs/schema"; | import type { FormatsPluginOptions } from '@feathersjs/schema'; | ||||||
|  |  | ||||||
| const formats: FormatsPluginOptions = [ | const formats: FormatsPluginOptions = [ | ||||||
|   "date-time", |     'date-time', | ||||||
|   "time", |     'time', | ||||||
|   "date", |     'date', | ||||||
|   "email", |     'email', | ||||||
|   "hostname", |     'hostname', | ||||||
|   "ipv4", |     'ipv4', | ||||||
|   "ipv6", |     'ipv6', | ||||||
|   "uri", |     'uri', | ||||||
|   "uri-reference", |     'uri-reference', | ||||||
|   "uuid", |     'uuid', | ||||||
|   "uri-template", |     'uri-template', | ||||||
|   "json-pointer", |     'json-pointer', | ||||||
|   "relative-json-pointer", |     'relative-json-pointer', | ||||||
|   "regex", |     'regex', | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| export const dataValidator: Ajv = addFormats(new Ajv({}), formats); | export const dataValidator: Ajv = addFormats(new Ajv({}), formats); | ||||||
|  |  | ||||||
| export const queryValidator: Ajv = addFormats( | export const queryValidator: Ajv = addFormats( | ||||||
|   new Ajv({ |     new Ajv({ | ||||||
|     coerceTypes: true, |         coerceTypes: true, | ||||||
|   }), |     }), | ||||||
|   formats, |     formats | ||||||
| ); | ); | ||||||
|   | |||||||
| @@ -1,40 +1,40 @@ | |||||||
| // For more information about this file see https://dove.feathersjs.com/guides/cli/app.test.html | // For more information about this file see https://dove.feathersjs.com/guides/cli/app.test.html | ||||||
| import assert from "assert"; | import assert from 'assert'; | ||||||
| import axios from "axios"; | import axios from 'axios'; | ||||||
| import type { Server } from "http"; | import type { Server } from 'http'; | ||||||
| import { app } from "../src/app"; | import { app } from '../src/app'; | ||||||
|  |  | ||||||
| const port = app.get("port"); | const port = app.get('port'); | ||||||
| const appUrl = `http://${app.get("host")}:${port}`; | const appUrl = `http://${app.get('host')}:${port}`; | ||||||
|  |  | ||||||
| describe("Feathers application tests", () => { | describe('Feathers application tests', () => { | ||||||
|   let server: Server; |     let server: Server; | ||||||
|  |  | ||||||
|   before(async () => { |     before(async () => { | ||||||
|     server = await app.listen(port); |         server = await app.listen(port); | ||||||
|   }); |     }); | ||||||
|  |  | ||||||
|   after(async () => { |     after(async () => { | ||||||
|     await app.teardown(); |         await app.teardown(); | ||||||
|   }); |     }); | ||||||
|  |  | ||||||
|   it("starts and shows the index page", async () => { |     it('starts and shows the index page', async () => { | ||||||
|     const { data } = await axios.get<string>(appUrl); |         const { data } = await axios.get<string>(appUrl); | ||||||
|  |  | ||||||
|     assert.ok(data.indexOf('<html lang="en">') !== -1); |         assert.ok(data.indexOf('<html lang="en">') !== -1); | ||||||
|   }); |     }); | ||||||
|  |  | ||||||
|   it("shows a 404 JSON error", async () => { |     it('shows a 404 JSON error', async () => { | ||||||
|     try { |         try { | ||||||
|       await axios.get(`${appUrl}/path/to/nowhere`, { |             await axios.get(`${appUrl}/path/to/nowhere`, { | ||||||
|         responseType: "json", |                 responseType: 'json', | ||||||
|       }); |             }); | ||||||
|       assert.fail("should never get here"); |             assert.fail('should never get here'); | ||||||
|     } catch (error: any) { |         } catch (error: any) { | ||||||
|       const { response } = error; |             const { response } = error; | ||||||
|       assert.strictEqual(response?.status, 404); |             assert.strictEqual(response?.status, 404); | ||||||
|       assert.strictEqual(response?.data?.code, 404); |             assert.strictEqual(response?.data?.code, 404); | ||||||
|       assert.strictEqual(response?.data?.name, "NotFound"); |             assert.strictEqual(response?.data?.name, 'NotFound'); | ||||||
|     } |         } | ||||||
|   }); |     }); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| // For more information about this file see https://dove.feathersjs.com/guides/cli/service.test.html | // For more information about this file see https://dove.feathersjs.com/guides/cli/service.test.html | ||||||
| import assert from "assert"; | import assert from 'assert'; | ||||||
| import { app } from "../../../src/app"; | import { app } from '../../../src/app'; | ||||||
|  |  | ||||||
| describe("aliases service", () => { | describe('aliases service', () => { | ||||||
|   it("registered the service", () => { |     it('registered the service', () => { | ||||||
|     const service = app.service("aliases"); |         const service = app.service('aliases'); | ||||||
|  |  | ||||||
|     assert.ok(service, "Registered the service"); |         assert.ok(service, 'Registered the service'); | ||||||
|   }); |     }); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| // For more information about this file see https://dove.feathersjs.com/guides/cli/service.test.html | // For more information about this file see https://dove.feathersjs.com/guides/cli/service.test.html | ||||||
| import assert from "assert"; | import assert from 'assert'; | ||||||
| import { app } from "../../../src/app"; | import { app } from '../../../src/app'; | ||||||
|  |  | ||||||
| describe("auth-oidc service", () => { | describe('auth-oidc service', () => { | ||||||
|   it("registered the service", () => { |     it('registered the service', () => { | ||||||
|     const service = app.service("auth-oidc"); |         const service = app.service('auth-oidc'); | ||||||
|  |  | ||||||
|     assert.ok(service, "Registered the service"); |         assert.ok(service, 'Registered the service'); | ||||||
|   }); |     }); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| // For more information about this file see https://dove.feathersjs.com/guides/cli/service.test.html | // For more information about this file see https://dove.feathersjs.com/guides/cli/service.test.html | ||||||
| import assert from "assert"; | import assert from 'assert'; | ||||||
| import { app } from "../../../../src/app"; | import { app } from '../../../../src/app'; | ||||||
|  |  | ||||||
| describe("auth-oidc/callback service", () => { | describe('auth-oidc/callback service', () => { | ||||||
|   it("registered the service", () => { |     it('registered the service', () => { | ||||||
|     const service = app.service("auth-oidc/callback"); |         const service = app.service('auth-oidc/callback'); | ||||||
|  |  | ||||||
|     assert.ok(service, "Registered the service"); |         assert.ok(service, 'Registered the service'); | ||||||
|   }); |     }); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -1,29 +0,0 @@ | |||||||
| // For more information about this file see https://dove.feathersjs.com/guides/cli/validators.html |  | ||||||
| import { Ajv, addFormats } from "@feathersjs/schema"; |  | ||||||
| import type { FormatsPluginOptions } from "@feathersjs/schema"; |  | ||||||
|  |  | ||||||
| const formats: FormatsPluginOptions = [ |  | ||||||
|   "date-time", |  | ||||||
|   "time", |  | ||||||
|   "date", |  | ||||||
|   "email", |  | ||||||
|   "hostname", |  | ||||||
|   "ipv4", |  | ||||||
|   "ipv6", |  | ||||||
|   "uri", |  | ||||||
|   "uri-reference", |  | ||||||
|   "uuid", |  | ||||||
|   "uri-template", |  | ||||||
|   "json-pointer", |  | ||||||
|   "relative-json-pointer", |  | ||||||
|   "regex", |  | ||||||
| ]; |  | ||||||
|  |  | ||||||
| export const dataValidator: Ajv = addFormats(new Ajv({}), formats); |  | ||||||
|  |  | ||||||
| export const queryValidator: Ajv = addFormats( |  | ||||||
|   new Ajv({ |  | ||||||
|     coerceTypes: true, |  | ||||||
|   }), |  | ||||||
|   formats, |  | ||||||
| ); |  | ||||||
		Reference in New Issue
	
	Block a user