Skip to content

Commit f1e81d2

Browse files
authored
Merge pull request #226 from jd1378/skip_hash_password
feat: add skipPasswordHash option
2 parents 791d161 + 1048652 commit f1e81d2

14 files changed

+269
-5
lines changed

src/methods/password-change.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export default async function passwordChange (
4040
app,
4141
identifyUserProps,
4242
passwordField,
43+
skipPasswordHash,
4344
sanitizeUserForClient,
4445
service,
4546
notifier
@@ -67,7 +68,7 @@ export default async function passwordChange (
6768
}
6869

6970
const patchedUser = await usersService.patch(user[usersServiceId] as Id, {
70-
password: await hashPassword(app, password, passwordField)
71+
password: skipPasswordHash ? password : await hashPassword(app, password, passwordField)
7172
}, Object.assign({}, params)) as User;
7273

7374
const userResult = await notify(notifier, 'passwordChange', patchedUser, notifierOptions);

src/methods/reset-password.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ async function resetPassword (
8585
skipIsVerifiedCheck,
8686
reuseResetToken,
8787
passwordField,
88+
skipPasswordHash,
8889
sanitizeUserForClient,
8990
notifier
9091
} = options;
@@ -160,7 +161,7 @@ async function resetPassword (
160161
}
161162

162163
const patchedUser = await usersService.patch(user[usersServiceId] as Id, {
163-
[passwordField]: await hashPassword(app, password, passwordField),
164+
[passwordField]: skipPasswordHash ? password : await hashPassword(app, password, passwordField),
164165
resetExpires: null,
165166
resetAttempts: null,
166167
resetToken: null,

src/methods/verify-signup-set-password.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ async function verifySignupSetPassword (
8585
const {
8686
app,
8787
passwordField,
88+
skipPasswordHash,
8889
sanitizeUserForClient,
8990
service,
9091
notifier
@@ -117,6 +118,7 @@ async function verifySignupSetPassword (
117118
isDateAfterNow(user.verifyExpires),
118119
user.verifyChanges || {},
119120
password,
121+
skipPasswordHash,
120122
params
121123
);
122124

@@ -149,17 +151,17 @@ async function verifySignupSetPassword (
149151
isVerified: boolean,
150152
verifyChanges: VerifyChanges,
151153
password: string,
154+
skipPasswordHash: boolean,
152155
params?: Params
153156
): Promise<User> {
154-
const hashedPassword = await hashPassword(app, password, passwordField);
155-
157+
156158
const patchData = Object.assign({}, verifyChanges || {}, {
157159
isVerified,
158160
verifyToken: null,
159161
verifyShortToken: null,
160162
verifyExpires: null,
161163
verifyChanges: {},
162-
[passwordField]: hashedPassword
164+
[passwordField]: skipPasswordHash ? password : await hashPassword(app, password, passwordField)
163165
});
164166

165167
const result = await usersService.patch(

src/options.ts

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export const optionsDefault: AuthenticationManagementServiceOptions = {
2626
sanitizeUserForClient,
2727
skipIsVerifiedCheck: false,
2828
passwordField: 'password',
29+
skipPasswordHash: false,
2930
passParams: undefined
3031
};
3132

src/services/PasswordChangeService.ts

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export class PasswordChangeService
2121
'identifyUserProps',
2222
'sanitizeUserForClient',
2323
'passwordField',
24+
'skipPasswordHash',
2425
'passParams'
2526
]);
2627
this.options = Object.assign(defaultOptions, options);

src/services/ResetPwdLongService.ts

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export class ResetPwdLongService
2121
'reuseResetToken',
2222
'sanitizeUserForClient',
2323
'passwordField',
24+
'skipPasswordHash',
2425
'passParams'
2526
]);
2627
this.options = Object.assign(defaultOptions, options);

src/services/ResetPwdShortService.ts

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export class ResetPwdShortService
2222
'reuseResetToken',
2323
'sanitizeUserForClient',
2424
'passwordField',
25+
'skipPasswordHash',
2526
'identifyUserProps',
2627
'passParams'
2728
]);

src/services/VerifySignupSetPasswordLongService.ts

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export class VerifySignupSetPasswordLongService
1818
'notifier',
1919
'sanitizeUserForClient',
2020
'passwordField',
21+
'skipPasswordHash',
2122
'passParams'
2223
]);
2324
this.options = Object.assign(defaultOptions, options);

src/services/VerifySignupSetPasswordShortService.ts

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export class VerifySignupSetPasswordShortService
1919
'notifier',
2020
'sanitizeUserForClient',
2121
'passwordField',
22+
'skipPasswordHash',
2223
'identifyUserProps',
2324
'passParams'
2425
]);

src/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ export interface AuthenticationManagementServiceOptions {
115115
/** Property name of the password field on your `'/users'` service
116116
* @default 'password' */
117117
passwordField: string
118+
/** Should we skip hashing password for `passwordField` ? If `true`, password won't be hashed by feathers-authentication-management when patching the user. This must be set to `true` if you are hashing your password field using resolvers. */
119+
skipPasswordHash: boolean
118120
/** Pass params from f-a-m service to `/users` service */
119121
passParams: (params) => Params | Promise<Params>
120122
}
@@ -139,6 +141,7 @@ export type VerifySignupSetPasswordLongServiceOptions = Pick<AuthenticationManag
139141
'sanitizeUserForClient' |
140142
'notifier' |
141143
'passwordField' |
144+
'skipPasswordHash' |
142145
'passParams'>;
143146
export type VerifySignupSetPasswordOptions = VerifySignupSetPasswordLongServiceOptions & { app: Application };
144147

@@ -148,6 +151,7 @@ export type PasswordChangeServiceOptions = Pick<AuthenticationManagementServiceO
148151
'notifier' |
149152
'sanitizeUserForClient' |
150153
'passwordField' |
154+
'skipPasswordHash' |
151155
'passParams'>;
152156
export type PasswordChangeOptions = PasswordChangeServiceOptions & { app: Application };
153157

@@ -162,6 +166,7 @@ export type ResetPasswordServiceOptions = Pick<AuthenticationManagementServiceOp
162166
'notifier' |
163167
'sanitizeUserForClient' |
164168
'passwordField' |
169+
'skipPasswordHash' |
165170
'passParams'>;
166171
export type ResetPasswordOptions = ResetPasswordServiceOptions & { app: Application };
167172

test/methods/password-change.test.ts

+95
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,101 @@ describe('password-change.ts', function () {
271271
});
272272
})
273273
});
274+
275+
[{
276+
name: "authManagement.create",
277+
callMethod: (app: Application, data: DataPasswordChange, params?: ParamsTest) => {
278+
return app.service("authManagement").create(withAction(data), params);
279+
}
280+
}, {
281+
name: "authManagement.passwordChange",
282+
callMethod: (app: Application, data: DataPasswordChange, params?: ParamsTest) => {
283+
return app.service("authManagement").passwordChange(data, params);
284+
}
285+
}, {
286+
name: "authManagement/password-change",
287+
callMethod: (app: Application, data: DataPasswordChange, params?: ParamsTest) => {
288+
return app.service("authManagement/password-change").create(data, params);
289+
}
290+
}].forEach(({ name, callMethod }) => {
291+
describe(`password-change.test.ts ${idType} skipPasswordHash ${name}`, () => {
292+
describe('standard', () => {
293+
let app: Application;
294+
let usersService: MemoryService;
295+
296+
beforeEach(async () => {
297+
app = feathers();
298+
app.use('authentication', authService(app));
299+
300+
const optionsUsers: Partial<MemoryServiceOptions> = {
301+
multi: true,
302+
id: idType
303+
};
304+
305+
app.use("users", new MemoryService(optionsUsers))
306+
307+
app.setup();
308+
309+
usersService = app.service('users');
310+
await usersService.remove(null);
311+
await usersService.create(clone(users));
312+
});
313+
314+
it('with skipPasswordHash false', async () => {
315+
app.configure(authLocalMgnt({
316+
passParams: params => params,
317+
skipPasswordHash: false,
318+
}));
319+
app.use("authManagement/password-change", new PasswordChangeService(app, {
320+
passParams: params => params,
321+
skipPasswordHash: false,
322+
}))
323+
324+
325+
const userRec = clone(users[1]);
326+
327+
const result = await callMethod(app, {
328+
user: {
329+
email: userRec.email
330+
},
331+
oldPassword: userRec.plainPassword,
332+
password: userRec.plainNewPassword
333+
}) as User;
334+
const user = await usersService.get(result[idType]);
335+
336+
assert.strictEqual(result.isVerified, true, 'isVerified not true');
337+
assert.notStrictEqual(user.password, result.plainNewPassword, 'password was not hashed');
338+
});
339+
340+
it('with skipPasswordHash true', async () => {
341+
app.configure(authLocalMgnt({
342+
passParams: params => params,
343+
skipPasswordHash: true,
344+
}));
345+
app.use("authManagement/password-change", new PasswordChangeService(app, {
346+
passParams: params => params,
347+
skipPasswordHash: true,
348+
}))
349+
350+
351+
const userRec = clone(users[1]);
352+
353+
const result = await callMethod(app, {
354+
user: {
355+
email: userRec.email
356+
},
357+
oldPassword: userRec.plainPassword,
358+
password: userRec.plainNewPassword
359+
}) as User;
360+
const user = await usersService.get(result[idType]);
361+
362+
assert.strictEqual(result.isVerified, true, 'isVerified not true');
363+
assert.strictEqual(user.password, result.plainNewPassword, 'password was hashed');
364+
});
365+
366+
});
367+
});
368+
})
274369
});
275370
});
276371

test/methods/reset-pwd-short.test.ts

+83
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,89 @@ const withAction = (
443443
});
444444
});
445445
});
446+
447+
describe('with skipPasswordHash', () => {
448+
let app: Application;
449+
let usersService: MemoryService;
450+
let authLocalMgntService: AuthenticationManagementService;
451+
452+
beforeEach(async () => {
453+
app = feathers();
454+
app.use('authentication', authService(app));
455+
456+
const optionsUsers: Partial<MemoryServiceOptions> = {
457+
multi: true,
458+
id: idType
459+
};
460+
if (pagination === "paginated") {
461+
optionsUsers.paginate = { default: 10, max: 50 };
462+
}
463+
app.use("users", new MemoryService(optionsUsers))
464+
465+
app.setup();
466+
467+
usersService = app.service('users');
468+
await usersService.remove(null);
469+
await usersService.create(clone(users));
470+
});
471+
472+
it("hashes password when skipPasswordHash is false", async () => {
473+
app.configure(
474+
authLocalMgnt({
475+
skipPasswordHash: false,
476+
})
477+
);
478+
app.use("authManagement/reset-password-short", new ResetPwdShortService(app, {
479+
skipPasswordHash: false,
480+
}));
481+
authLocalMgntService = app.service('authManagement');
482+
483+
484+
const result = await callMethod(app, {
485+
token: '00099',
486+
password: '123456',
487+
user: {
488+
email: users[0].email
489+
},
490+
notifierOptions: {transport: 'sms'},
491+
}) as User;
492+
const user = await usersService.get(result[idType]);
493+
494+
assert.notStrictEqual(
495+
user.password,
496+
'123456',
497+
'password was not hashed'
498+
);
499+
});
500+
501+
it("does not hash password when skipPasswordHash is true", async () => {
502+
app.configure(
503+
authLocalMgnt({
504+
skipPasswordHash: true,
505+
})
506+
);
507+
app.use("authManagement/reset-password-short", new ResetPwdShortService(app, {
508+
skipPasswordHash: true,
509+
}));
510+
authLocalMgntService = app.service('authManagement');
511+
512+
const result = await callMethod(app, {
513+
token: '00099',
514+
password: '123456',
515+
user: {
516+
email: users[0].email
517+
},
518+
notifierOptions: {transport: 'sms'},
519+
}) as User;
520+
const user = await usersService.get(result[idType]);
521+
522+
assert.strictEqual(
523+
user.password,
524+
'123456',
525+
'password was hashed'
526+
);
527+
});
528+
});
446529
});
447530
});
448531
});

0 commit comments

Comments
 (0)