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" | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								index.ts
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								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); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -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()], | ||||||
| }); | }); | ||||||
|   | |||||||
							
								
								
									
										44
									
								
								src/app.ts
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								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()); | ||||||
|  |  | ||||||
| @@ -28,8 +20,8 @@ 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()); | ||||||
| @@ -39,7 +31,7 @@ app.use( | |||||||
|         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 | ||||||
| @@ -53,16 +45,16 @@ app.use(function (req, _res, 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); | ||||||
|   | |||||||
| @@ -1,34 +1,31 @@ | |||||||
| // 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", |  | ||||||
|     (authResult: AuthenticationResult, { connection }: Params) => { |  | ||||||
|         // connection can be undefined if there is no |         // connection can be undefined if there is no | ||||||
|         // real-time connection, e.g. when logging in via REST |         // real-time connection, e.g. when logging in via REST | ||||||
|         if (connection) { |         if (connection) { | ||||||
|             // The connection is no longer anonymous, remove it |             // The connection is no longer anonymous, remove it | ||||||
|         app.channel("anonymous").leave(connection); |             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) => { | ||||||
| @@ -36,6 +33,6 @@ export const channels = (app: Application) => { | |||||||
|         // 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,5 +1,5 @@ | |||||||
| 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 { | ||||||
| @@ -9,7 +9,7 @@ export const logError = async (context: HookContext, next: NextFunction) => { | |||||||
|  |  | ||||||
|         // 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'); | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								src/index.ts
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								src/index.ts
									
									
									
									
									
								
							| @@ -1,12 +1,10 @@ | |||||||
| 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,14 +1,10 @@ | |||||||
| 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; | ||||||
| @@ -56,74 +52,57 @@ export class AliasesService<ServiceParams extends AliasesParams = AliasesParams> | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async create(data: AliasesData, params: ServiceParams): Promise<Alias>; |     async create(data: AliasesData, params: ServiceParams): Promise<Alias>; | ||||||
|   async create( |     async create(data: AliasesData, params: ServiceParams): Promise<Alias | Alias[]> { | ||||||
|     data: AliasesData, |  | ||||||
|     params: ServiceParams, |  | ||||||
|   ): Promise<Alias | Alias[]> { |  | ||||||
|         const userId = await this.getUserIdByEmailAddress(params); |         const userId = await this.getUserIdByEmailAddress(params); | ||||||
|  |  | ||||||
|         const randomString = faker.git.commitSha({ length: 4 }); |         const randomString = faker.git.commitSha({ length: 4 }); | ||||||
|  |  | ||||||
|         // Replace all non-alphanumeric characters with nothing and spaces with dashes |         // Replace all non-alphanumeric characters with nothing and spaces with dashes | ||||||
|     const alias = |         const alias = `${faker.color.human()}-${faker.animal.snake()}-${randomString}` | ||||||
|       `${faker.color.human()}-${faker.animal.snake()}-${randomString}` |             .replace(/\s+/g, '-') | ||||||
|         .replace(/\s+/g, "-") |             .replace(/[^a-zA-Z0-9-]/g, '') | ||||||
|         .replace(/[^a-zA-Z0-9-]/g, "") |  | ||||||
|             .toLowerCase(); |             .toLowerCase(); | ||||||
|  |  | ||||||
|     const emailDomain = config.get("wildDuck.domain"); |         const emailDomain = config.get('wildDuck.domain'); | ||||||
|  |  | ||||||
|     const createResult = await wildDuckClient.post<CreateAddressResponse>( |         const createResult = await wildDuckClient.post<CreateAddressResponse>(`/users/${userId}/addresses`, { | ||||||
|       `/users/${userId}/addresses`, |  | ||||||
|       { |  | ||||||
|             address: `${alias}@${emailDomain}`, |             address: `${alias}@${emailDomain}`, | ||||||
|       }, |         }); | ||||||
|     ); |  | ||||||
|  |  | ||||||
|         if (!createResult.data.success) { |         if (!createResult.data.success) { | ||||||
|       throw new BadRequest("Failed to create alias"); |             throw new BadRequest('Failed to create alias'); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return this.getUserAddresses(userId); |         return this.getUserAddresses(userId); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   private async getUserIdByEmailAddress( |     private async getUserIdByEmailAddress(params: ServiceParams): Promise<string> { | ||||||
|     params: ServiceParams, |  | ||||||
|   ): Promise<string> { |  | ||||||
|         const emails = params.session?.user?.emails; |         const emails = params.session?.user?.emails; | ||||||
|  |  | ||||||
|         const addressInfoResponse = await Promise.any( |         const addressInfoResponse = await Promise.any( | ||||||
|             emails |             emails | ||||||
|       .filter((email: string) => email.endsWith(config.get("wildDuck.preferredDomain"))) |                 .filter((email: string) => email.endsWith(config.get('wildDuck.preferredDomain'))) | ||||||
|       .map((email: string) => |                 .map((email: string) => wildDuckClient.get<Alias>(`addresses/resolve/${email}`)) | ||||||
|         wildDuckClient.get<Alias>(`addresses/resolve/${email}`), |  | ||||||
|       ), |  | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         return addressInfoResponse.data.user; |         return addressInfoResponse.data.user; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private async getUserAddresses(userId: string): Promise<Alias[]> { |     private async getUserAddresses(userId: string): Promise<Alias[]> { | ||||||
|     const { data: userAddressesResponse } = |         const { data: userAddressesResponse } = await wildDuckClient.get<GetAddressInfoResponse>( | ||||||
|       await wildDuckClient.get<GetAddressInfoResponse>( |             `/users/${userId}/addresses` | ||||||
|         `/users/${userId}/addresses`, |  | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         return userAddressesResponse.results; |         return userAddressesResponse.results; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async remove(id: NullableId, params: ServiceParams): Promise<Alias[]> { |     async remove(id: NullableId, params: ServiceParams): Promise<Alias[]> { | ||||||
|     const { data: addressInfoResponse } = await wildDuckClient.get<Alias>( |         const { data: addressInfoResponse } = await wildDuckClient.get<Alias>(`addresses/resolve/${id}`); | ||||||
|       `addresses/resolve/${id}`, |         const allowedDomain: string = config.get('wildDuck.domain'); | ||||||
|     ); |  | ||||||
|     const allowedDomain: string = config.get("wildDuck.domain"); |  | ||||||
|  |  | ||||||
|         // If address does not match the allowed domain, throw an error |         // If address does not match the allowed domain, throw an error | ||||||
|     if ( |         if (!allowedDomain || !addressInfoResponse.address.endsWith(allowedDomain)) { | ||||||
|       !allowedDomain || |             throw new BadRequest('Unable to delete address'); | ||||||
|       !addressInfoResponse.address.endsWith(allowedDomain) |  | ||||||
|     ) { |  | ||||||
|       throw new BadRequest("Unable to delete address"); |  | ||||||
|         } |         } | ||||||
|         const userId = await this.getUserIdByEmailAddress(params); |         const userId = await this.getUserIdByEmailAddress(params); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| 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)), { | ||||||
| @@ -32,7 +32,7 @@ export const aliases = (app: Application) => { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| // 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; | ||||||
| @@ -18,29 +18,28 @@ 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; | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| 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 | ||||||
| @@ -18,7 +18,7 @@ export const authOidc = (app: Application) => { | |||||||
|         }, |         }, | ||||||
|         (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({ | ||||||
| @@ -38,7 +38,7 @@ export const authOidc = (app: Application) => { | |||||||
|     }); |     }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| declare module "../../declarations" { | declare module '../../declarations' { | ||||||
|     interface ServiceTypes { |     interface ServiceTypes { | ||||||
|         [authOidcPath]: AuthOidcService; |         [authOidcPath]: AuthOidcService; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,20 +1,15 @@ | |||||||
| 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; | ||||||
| @@ -28,38 +23,31 @@ export interface AuthOidcCallbackParams extends Params<AuthOidcCallbackQuery> { | |||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| 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 '/'; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,14 +1,11 @@ | |||||||
| 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 | ||||||
| @@ -22,7 +19,7 @@ export const authOidcCallback = (app: Application) => { | |||||||
|         }, |         }, | ||||||
|         (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({ | ||||||
| @@ -42,7 +39,7 @@ export const authOidcCallback = (app: Application) => { | |||||||
|     }); |     }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| declare module "../../../declarations" { | declare module '../../../declarations' { | ||||||
|     interface ServiceTypes { |     interface ServiceTypes { | ||||||
|         [authOidcCallbackPath]: AuthOidcCallbackService; |         [authOidcCallbackPath]: AuthOidcCallbackService; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| 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); | ||||||
|   | |||||||
| @@ -1,22 +1,22 @@ | |||||||
| // 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); | ||||||
| @@ -25,5 +25,5 @@ export const queryValidator: Ajv = addFormats( | |||||||
|     new Ajv({ |     new Ajv({ | ||||||
|         coerceTypes: true, |         coerceTypes: true, | ||||||
|     }), |     }), | ||||||
|   formats, |     formats | ||||||
| ); | ); | ||||||
|   | |||||||
| @@ -1,13 +1,13 @@ | |||||||
| // 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 () => { | ||||||
| @@ -18,23 +18,23 @@ describe("Feathers application tests", () => { | |||||||
|         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