Skip to content

Commit d135526

Browse files
authored
fix(authentication-local): Local Auth - Nested username & Password fields (#3091)
1 parent 3742028 commit d135526

File tree

4 files changed

+41
-11
lines changed

4 files changed

+41
-11
lines changed

docs/api/authentication/local.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ export const authentication = (app: Application) => {
5151

5252
Options are set in the [authentication configuration](./service.md#configuration) under the strategy name. Available options are:
5353

54-
- `usernameField`: Name of the username field (e.g. `'email'`)
55-
- `passwordField`: Name of the password field (e.g. `'password'`)
54+
- `usernameField`: Name of the username field (e.g. `'email'`), may be a nested property (e.g. `'auth.email'`)
55+
- `passwordField`: Name of the password field (e.g. `'password'`), may be a nested property (e.g. `'auth.password'`)
5656
- `hashSize` (default: `10`): The BCrypt salt length
5757
- `errorMessage` (default: `'Invalid login'`): The error message to return on errors
5858
- `entityUsernameField` (default: `usernameField`): Name of the username field on the entity if authentication request data and entity field names are different

packages/authentication-local/src/strategy.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,16 +122,15 @@ export class LocalStrategy extends AuthenticationBaseStrategy {
122122

123123
async authenticate(data: AuthenticationRequest, params: Params) {
124124
const { passwordField, usernameField, entity, errorMessage } = this.configuration
125-
const username = data[usernameField]
126-
const password = data[passwordField]
125+
const username = get(data, usernameField)
126+
const password = get(data, passwordField)
127127

128128
if (!password) {
129129
// exit early if there is no password
130130
throw new NotAuthenticated(errorMessage)
131131
}
132132

133133
const result = await this.findEntity(username, omit(params, 'provider'))
134-
135134
await this.comparePassword(result, password)
136135

137136
return {

packages/authentication-local/test/fixture.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,13 @@ export type ServiceTypes = {
1010
users: MemoryService
1111
}
1212

13-
export function createApplication(app = feathers<ServiceTypes>()) {
13+
export function createApplication(
14+
app = feathers<ServiceTypes>(),
15+
authOptionOverrides: Record<string, unknown> = {}
16+
) {
1417
const authentication = new AuthenticationService(app)
1518

16-
app.set('authentication', {
19+
const authConfig = {
1720
entity: 'user',
1821
service: 'users',
1922
secret: 'supersecret',
@@ -22,8 +25,10 @@ export function createApplication(app = feathers<ServiceTypes>()) {
2225
local: {
2326
usernameField: 'email',
2427
passwordField: 'password'
25-
}
26-
})
28+
},
29+
...authOptionOverrides
30+
}
31+
app.set('authentication', authConfig)
2732

2833
authentication.register('jwt', new JWTStrategy())
2934
authentication.register('local', new LocalStrategy())
@@ -40,9 +45,9 @@ export function createApplication(app = feathers<ServiceTypes>()) {
4045
})
4146
)
4247

43-
app.service('users').hooks([protect('password')])
48+
app.service('users').hooks([protect(authConfig.local.passwordField)])
4449
app.service('users').hooks({
45-
create: [hashPassword('password')],
50+
create: [hashPassword(authConfig.local.passwordField)],
4651
get: [
4752
async (context, next) => {
4853
await next()

packages/authentication-local/test/strategy.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,4 +195,30 @@ describe('@feathersjs/authentication-local/strategy', () => {
195195

196196
assert.notStrictEqual(resolvedData.password, 'supersecret')
197197
})
198+
it('should allow for nested values in the usernameField', async () => {
199+
const appWithNestedFieldOverride = createApplication(undefined, {
200+
local: {
201+
usernameField: 'auth.email',
202+
passwordField: 'auth.password'
203+
}
204+
})
205+
const nestedUser = await appWithNestedFieldOverride.service('users').create({ auth: { email, password } })
206+
const authService = appWithNestedFieldOverride.service('authentication')
207+
const authResult = await authService.create({
208+
strategy: 'local',
209+
auth: {
210+
email,
211+
password
212+
}
213+
})
214+
const { accessToken } = authResult
215+
216+
assert.ok(accessToken)
217+
assert.strictEqual(authResult.user.auth.email, email)
218+
219+
const decoded = await authService.verifyAccessToken(accessToken)
220+
221+
assert.strictEqual(decoded.sub, `${nestedUser.id}`)
222+
//
223+
})
198224
})

0 commit comments

Comments
 (0)