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;