Skip to content

Commit

Permalink
feat(API/login): permissive email handling
Browse files Browse the repository at this point in the history
Allow case insensitive email when there's no other candidate.

closes #6570
  • Loading branch information
kontrollanten committed Oct 2, 2024
1 parent 714d9c4 commit 5e44b71
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 4 deletions.
15 changes: 14 additions & 1 deletion packages/tests/src/api/users/oauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ describe('Test oauth', function () {
})

await setAccessTokensToServers([ server ])
await server.users.create({ username: 'user1', email: 'user@example.com' })
await server.users.create({ username: 'user2', email: 'User@example.com', password: 'AdvancedPassword' })

sqlCommand = new SQLCommand(server)
})
Expand Down Expand Up @@ -79,14 +81,17 @@ describe('Test oauth', function () {
})

it('Should not login with an invalid password', async function () {
const user = { username: server.store.user.username, password: 'mew_three' }
const user = { username: 'User@example.com', password: 'password' }
const body = await server.login.login({ user, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })

expectInvalidCredentials(body)
})

it('Should be able to login', async function () {
await server.login.login({ expectedStatus: HttpStatusCode.OK_200 })

const user = { username: 'User@example.com', password: 'AdvancedPassword' }
await server.login.login({ user, expectedStatus: HttpStatusCode.OK_200 })
})

it('Should be able to login with an insensitive username', async function () {
Expand All @@ -99,6 +104,14 @@ describe('Test oauth', function () {
const user3 = { username: 'ROOt', password: server.store.user.password }
await server.login.login({ user: user3, expectedStatus: HttpStatusCode.OK_200 })
})

it('Should be able to login with an insensitive email when no similar emails exist', async function () {
const user = { username: 'ADMIN' + server.internalServerNumber + '@example.com', password: server.store.user.password }
await server.login.login({ user, expectedStatus: HttpStatusCode.OK_200 })

const user2 = { username: 'admin' + server.internalServerNumber + '@example.com', password: server.store.user.password }
await server.login.login({ user: user2, expectedStatus: HttpStatusCode.OK_200 })
})
})

describe('Logout', function () {
Expand Down
13 changes: 10 additions & 3 deletions server/core/lib/auth/oauth-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { OAuthClientModel } from '../../models/oauth/oauth-client.js'
import { OAuthTokenModel } from '../../models/oauth/oauth-token.js'
import { UserModel } from '../../models/user/user.js'
import { findAvailableLocalActorName } from '../local-actor.js'
import { buildUser, createUserAccountAndChannelAndPlaylist } from '../user.js'
import { buildUser, createUserAccountAndChannelAndPlaylist, getUserByEmailPermissive } from '../user.js'
import { ExternalUser } from './external-auth.js'
import { TokensCache } from './tokens-cache.js'

Expand Down Expand Up @@ -87,7 +87,7 @@ async function getUser (usernameOrEmail?: string, password?: string, bypassLogin
if (bypassLogin && bypassLogin.bypass === true) {
logger.info('Bypassing oauth login by plugin %s.', bypassLogin.pluginName)

let user = await UserModel.loadByEmail(bypassLogin.user.email)
let user = getUserByEmailPermissive(await UserModel.loadByEmailCaseInsensitive(bypassLogin.user.email), bypassLogin.user.email)

if (!user) {
user = await createUserFromExternal(bypassLogin.pluginName, bypassLogin.user)
Expand Down Expand Up @@ -119,7 +119,14 @@ async function getUser (usernameOrEmail?: string, password?: string, bypassLogin

logger.debug('Getting User (username/email: ' + usernameOrEmail + ', password: ******).')

const user = await UserModel.loadByUsernameOrEmail(usernameOrEmail)
const users = await UserModel.loadByUsernameOrEmailCaseInsensitive(usernameOrEmail)
let user: MUserDefault

if (usernameOrEmail.includes('@')) {
user = getUserByEmailPermissive(users, usernameOrEmail)
} else {
user = users[0]
}

// If we don't find the user, or if the user belongs to a plugin
if (!user || user.pluginAuth !== null || !password) return null
Expand Down
14 changes: 14 additions & 0 deletions server/core/models/user/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,20 @@ export class UserModel extends SequelizeModel<UserModel> {
return UserModel.findOne(query)
}

static loadByUsernameOrEmailCaseInsensitive (usernameOrEmail: string): Promise<MUserDefault[]> {
const query = {
where: {
[Op.or]: [
where(fn('lower', col('username')), fn('lower', usernameOrEmail) as any),

where(fn('lower', col('email')), fn('lower', usernameOrEmail) as any)
]
}
}

return UserModel.findAll(query)
}

static loadByVideoId (videoId: number): Promise<MUserDefault> {
const query = {
include: [
Expand Down

0 comments on commit 5e44b71

Please sign in to comment.