payloadcms / payload

Payload is the open-source, fullstack Next.js framework, giving you instant backend superpowers. Get a full TypeScript backend and admin panel instantly. Use Payload as a headless CMS or for building powerful applications.
https://payloadcms.com
MIT License
23.58k stars 1.5k forks source link

"disableLocalStrategy: true" still asking for email or nickname #8073

Open antonbrams opened 3 weeks ago

antonbrams commented 3 weeks ago

Environment Info

    "next": "15.0.0-canary.104",
    "payload": "beta",
    "nodejs": v18.20.4

Describe the Bug

I want to create a custom auth for trainees that can login with their firstname, lastname and trainee_id. There is no need for password, email or nicknames. I've find out that disableLocalStrategy set to true is ignored and still requires email/nickname and password. If i'm doing something wrong, please tell me how to fix it?

Reproduction Steps

import type {CollectionConfig} from 'payload'

export default {
    slug: 'trainees',
    labels: {
        singular: 'Azubi',
        plural: 'Azubis',
    },
    auth: {
        disableLocalStrategy: true,
        strategies: [
            {
                identityField: 'trainee_id',
                name: 'custom-strategy',
                async authenticate({headers, payload}) {
                    const {docs} = await payload.find({
                        collection: 'trainees',
                        where: {
                            name_first: {equals: headers.get('name_first')},
                            name_last: {equals: headers.get('name_last')},
                            trainee_id: {equals: headers.get('trainee_id')},
                        },
                    })
                    return {user: docs[0]}
                },
            },
        ],
    },
    fields: [
        {
            name: 'name_first',
            label: 'Vorname',
            type: 'text',
            required: true,
        },
        {
            name: 'name_last',
            label: 'Nachname',
            type: 'text',
            required: true,
        },
        {
            name: 'trainee_id',
            label: 'Matrikelnummer',
            type: 'text',
            required: true,
        },
    ],
} as const satisfies CollectionConfig

is called by

    const result = await payload.login({
        collection: 'trainees',
        data: {
            name_first: 'Hassan',
            name_last: 'Altay',
            trainee_id: '43523452345',
        },
    })
    console.log(result)

causes

dock-payload   |  ⨯ node_modules/payload/dist/auth/operations/login.js (56:1) @ loginOperation
dock-payload   |  ⨯ ValidationError: The following field is invalid: email
dock-payload   |     at async $$ACTION_0 (./src/app/logic/actions.ts:52:20)
dock-payload   | digest: "1954918542"
dock-payload   | Cause: {
dock-payload   |   collection: 'trainees',
dock-payload   |   errors: [ { field: 'email', message: 'This field is required.' } ]
dock-payload   | }
dock-payload   |   54 |         // cannot login with username, did not provide email
dock-payload   |   55 |         if (!canLoginWithUsername && !sanitizedEmail) {
dock-payload   | > 56 |             throw new ValidationError({
dock-payload   |      | ^
dock-payload   |   57 |                 collection: collectionConfig.slug,
dock-payload   |   58 |                 errors: [
dock-payload   |   59 |                     {
antonbrams commented 2 weeks ago

can somebody help me with this, please? :)

geminigeek commented 2 weeks ago

hi , you are reading headers in custom strategies ?!!

antonbrams commented 2 weeks ago

hi , you are reading headers in custom strategies ?!!

yes, i can pass my own headers as you see

name_first: {equals: headers.get('name_first')},
name_last: {equals: headers.get('name_last')},
trainee_id: {equals: headers.get('trainee_id')},

but this parameter disableLocalStrategy: true doesn't have any effect, the payload still requires email/nickname and password field

geminigeek commented 2 weeks ago

hi,

i didn't see that your setting headers, it seems strategy are an array so they are fired one after another till any of them fulfills the record , i am build a custom strategy, looking forward to a solution too

ffd114 commented 6 days ago

I think the custom strategies is not supposed to use with login auth operation. It's for accessing the API directly without doing the login auth operation. I ended up just creating custom login root endpoint based on the source here https://github.com/payloadcms/payload/blob/v2.28.0/packages/payload/src/auth/operations/login.ts#L43

import { CookieOptions, Response } from 'express'
import jwt from 'jsonwebtoken'
import { PayloadRequest } from 'payload/types'

const getCookieExpiration = (seconds = 7200) => {
  const currentTime = new Date()
  currentTime.setSeconds(currentTime.getSeconds() + seconds)
  return currentTime
}

export async function handleCustomLogin(req: PayloadRequest, res: Response) {
  const { payload, body: credentials } = req

  const user = ... // get user here

  if (!user) return res.status(401).send('User not found')

  const { config, secret } = payload
  const collectionConfig = payload.collections['users'].config

  const fieldsToSign = {
    id: user.id,
    collection: 'users',
    email: user.email,
  }
  const token = jwt.sign(fieldsToSign, secret, {
    expiresIn: collectionConfig.auth.tokenExpiration,
  })

  const cookieOptions: CookieOptions = {
    domain: undefined,
    expires: getCookieExpiration(collectionConfig.auth.tokenExpiration),
    httpOnly: true,
    path: '/',
    sameSite: collectionConfig.auth.cookies.sameSite,
    secure: collectionConfig.auth.cookies.secure,
  }

  if (collectionConfig.auth.cookies.domain) cookieOptions.domain = collectionConfig.auth.cookies.domain

  res.cookie(`${config.cookiePrefix}-token`, token, cookieOptions)

  const result = {
    exp: (jwt.decode(token) as jwt.JwtPayload).exp,
    token,
    user,
  }
  return res.json(result)
}

Please be aware that I strip a lot of function from the upstream source, so it might cause some unintended effect