/ src / authentication.ts
authentication.ts
  1  import {
  2  	type AuthenticationResult,
  3  	AuthenticationService,
  4  	JWTStrategy,
  5  } from "@feathersjs/authentication";
  6  import { LocalStrategy } from "@feathersjs/authentication-local";
  7  import type { Params } from "@feathersjs/feathers";
  8  
  9  import type { JsonObject } from "swagger-ui-express";
 10  import type { Application } from "./declarations";
 11  import logger from "./logger";
 12  
 13  declare module "./declarations" {
 14  	interface AuthServiceTypes {
 15  		authentication: AuthenticationService;
 16  	}
 17  }
 18  
 19  class MyAuthenticationService extends AuthenticationService {
 20  	/**
 21  	 * @swagger
 22  	 * components:
 23  	 *   schemas:
 24  	 *     User:
 25  	 *       type: object
 26  	 *       properties:
 27  	 *         id:
 28  	 *           type: string
 29  	 *         username:
 30  	 *           type: string
 31  	 *         email:
 32  	 *           type: string
 33  	 *         role:
 34  	 *           type: string
 35  	 *         createdAt:
 36  	 *           type: string
 37  	 *         updatedAt:
 38  	 *           type: string
 39  	 *       required:
 40  	 *         - id
 41  	 *         - username
 42  	 *         - email
 43  	 *         - role
 44  	 *         - createdAt
 45  	 *         - updatedAt
 46  	 *     AuthenticationStrategy:
 47  	 *       type: object
 48  	 *       properties:
 49  	 *         strategy:
 50  	 *           type: string
 51  	 *       required:
 52  	 *         - strategy
 53  	 *     AuthResultAuthentication:
 54  	 *       type: object
 55  	 *       properties:
 56  	 *         strategy:
 57  	 *           type: string
 58  	 *       required:
 59  	 *         - strategy
 60  	 *     AuthResultPayload:
 61  	 *       type: object
 62  	 *       properties:
 63  	 *         authentication:
 64  	 *           $ref: '#/components/schemas/AuthResultAuthentication'
 65  	 *         user:
 66  	 *           $ref: '#/components/schemas/User'
 67  	 *       required:
 68  	 *         - authentication
 69  	 *         - user
 70  	 *     AuthenticationPayload:
 71  	 *       type: object
 72  	 *       properties:
 73  	 *         authResult:
 74  	 *           $ref: '#/components/schemas/AuthResultPayload'
 75  	 *         iat:
 76  	 *           type: integer
 77  	 *         exp:
 78  	 *           type: integer
 79  	 *         aud:
 80  	 *           type: string
 81  	 *         iss:
 82  	 *           type: string
 83  	 *         jti:
 84  	 *           type: string
 85  	 *       required:
 86  	 *         - authResult
 87  	 *         - iat
 88  	 *         - exp
 89  	 *         - aud
 90  	 *         - iss
 91  	 *         - jti
 92  	 *     Authentication:
 93  	 *       type: object
 94  	 *       properties:
 95  	 *         strategy:
 96  	 *           type: string
 97  	 *         payload:
 98  	 *           $ref: '#/components/schemas/AuthenticationPayload'
 99  	 *       required:
100  	 *         - strategy
101  	 *         - payload
102  	 *     AuthResult:
103  	 *       type: object
104  	 *       properties:
105  	 *         accessToken:
106  	 *           type: string
107  	 *         authentication:
108  	 *           $ref: '#/components/schemas/Authentication'
109  	 *         user:
110  	 *           $ref: '#/components/schemas/User'
111  	 *       required:
112  	 *         - accessToken
113  	 *         - authentication
114  	 *         - user
115  	 */
116  	async getPayload(
117  		authResult: AuthenticationResult,
118  		params: Params,
119  	): Promise<AuthenticationResult> {
120  		// Call original `getPayload` first
121  		const payload = await super.getPayload(authResult, params);
122  
123  		if (!authResult) {
124  			throw new Error("No AuthenticationResult found");
125  		}
126  
127  		return Object.assign(payload, {
128  			authResult: replaceUndefinedKeys(authResult),
129  		});
130  	}
131  }
132  
133  type JsonValue = string | number | boolean | JsonObject | JsonArray | null;
134  
135  type JsonArray = JsonValue[];
136  
137  // That's some unfortunate hack! Unsolved mystery: AuthenticationResult for some reasons..
138  // places undefined keys. I couldn't see why and found it  safe to just replace with user key
139  // until I find the culprit/reason
140  const replaceUndefinedKeys = (
141  	obj: AuthenticationResult,
142  ): AuthenticationResult => {
143  	if (obj && typeof obj === "object") {
144  		Object.keys(obj).forEach((key) => {
145  			const value = obj[key];
146  			if (typeof value === "object" && value !== null) {
147  				replaceUndefinedKeys(value as AuthenticationResult);
148  			}
149  			if (key === "undefined") {
150  				obj.user = value;
151  				delete obj[key];
152  			}
153  		});
154  	}
155  	return obj;
156  };
157  
158  /**
159   * @swagger
160   * tags:
161   *   name: Authentication
162   *   description: Authentication management
163   */
164  
165  /**
166   * @swagger
167   * /authentication:
168   *   post:
169   *     summary: Authenticate a user. Make sure to create that user first
170   *     tags: [Authentication]
171   *     requestBody:
172   *       required: true
173   *       content:
174   *         application/json:
175   *           schema:
176   *             type: object
177   *             required:
178   *               - strategy
179   *               - email
180   *               - password
181   *             properties:
182   *               strategy:
183   *                 type: string
184   *                 enum: [local, jwt]  # Set enum values for strategy
185   *                 default: local      # Set default value to "local"
186   *               email:
187   *                 type: string
188   *                 format: email
189   *               password:
190   *                 type: string
191   *     responses:
192   *       201:
193   *         description: Authentication successful
194   *         content:
195   *           application/json:
196   *             schema:
197   *               $ref: '#/components/schemas/AuthResult'
198  
199   *              
200   */
201  export function configureStrategies(app: Application): void {
202  	logger.info("configuring local and jwt strategies");
203  	const authentication = new MyAuthenticationService(app);
204  
205  	authentication.register("jwt", new JWTStrategy());
206  
207  	app.use("authentication", authentication);
208  	// Ensure settings are correctly applied to the authentication service
209  	app.set("authentication", {
210  		secret: app.get("authentication").secret || "1234",
211  		strategies: ["jwt", "local"],
212  		authStrategies: ["jwt", "local"], // Ensure this line is included
213  		service: "users",
214  		path: "/authentication",
215  		local: {
216  			usernameField: "email", // or 'username' based on your user model
217  			passwordField: "password",
218  		},
219  	});
220  
221  	authentication.register("local", new LocalStrategy());
222  
223  	// Optionally, reconfigure the authentication service to ensure it picks up the settings
224  	// im not sure about this, it does get picked and fails if we set it.
225  	//app.configure(authentication);
226  }
227  
228  export default configureStrategies;