feat(API/login): permissive email handling

Allow case insensitive email when there's no other candidate.

closes #6570
This commit is contained in:
kontrollanten 2024-10-01 23:20:01 +02:00
parent 714d9c4aa7
commit 5e44b71940
3 changed files with 38 additions and 4 deletions

View File

@ -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)
})
@ -79,7 +81,7 @@ 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)
@ -87,6 +89,9 @@ describe('Test oauth', function () {
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 () {
@ -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 () {

View File

@ -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'
@ -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)
@ -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

View File

@ -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: [