Compare commits

..

1 Commits

Author SHA1 Message Date
PatrikKozak
9217599731 fix: passes onSave to DocumentDrawerin multi-value label for relationship field 2024-07-17 20:40:05 -04:00
99 changed files with 1164 additions and 1749 deletions

View File

@@ -83,50 +83,12 @@ The following options are available:
| **`disableLocalStrategy`** | Advanced - disable Payload's built-in local auth strategy. Only use this property if you have replaced Payload's auth mechanisms with your own. |
| **`forgotPassword`** | Customize the way that the `forgotPassword` operation functions. [More details](./email#forgot-password). |
| **`lockTime`** | Set the time (in milliseconds) that a user should be locked out if they fail authentication more times than `maxLoginAttempts` allows for. |
| **`loginWithUsername`** | Ability to allow users to login with username/password. [More](/docs/authentication/overview#login-with-username) |
| **`maxLoginAttempts`** | Only allow a user to attempt logging in X amount of times. Automatically locks out a user from authenticating if this limit is passed. Set to `0` to disable. |
| **`strategies`** | Advanced - an array of custom authentification strategies to extend this collection's authentication with. [More details](./custom-strategies). |
| **`tokenExpiration`** | How long (in seconds) to keep the user logged in. JWTs and HTTP-only cookies will both expire at the same time. |
| **`useAPIKey`** | Payload Authentication provides for API keys to be set on each user within an Authentication-enabled Collection. [More details](./api-keys). |
| **`verify`** | Set to `true` or pass an object with verification options to require users to verify by email before they are allowed to log into your app. [More details](./email#email-verification). |
### Login With Username
You can allow users to login with their username instead of their email address by setting the `loginWithUsername` property to `true`.
Example:
```ts
{
slug: 'customers',
auth: {
loginWithUsername: true,
},
}
```
Or, you can pass an object with additional options:
```ts
{
slug: 'customers',
auth: {
loginWithUsername: {
allowEmailLogin: true, // default: false
requireEmail: false, // default: false
},
},
}
```
**`allowEmailLogin`**
If set to `true`, users can log in with either their username or email address. If set to `false`, users can only log in with their username.
**`requireEmail`**
If set to `true`, an email address is required when creating a new user. If set to `false`, email is not required upon creation.
## Admin Auto-Login
For testing and demo purposes you may want to skip forcing the admin user to login in order to access the [Admin Panel](../admin/overview). Typically, all users should be required to login to access the Admin Panel, however, you can speed up local development time by enabling auto-login.

View File

@@ -1,6 +1,6 @@
{
"name": "payload-monorepo",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"private": true,
"type": "module",
"scripts": {

View File

@@ -1,6 +1,6 @@
{
"name": "create-payload-app",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-mongodb",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "The officially supported MongoDB database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/db-postgres",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "The officially supported Postgres database adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-nodemailer",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "Payload Nodemailer Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/email-resend",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "Payload Resend Email Adapter",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/graphql",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -8,7 +8,7 @@ function unlockResolver(collection: Collection) {
async function resolver(_, args, context: Context) {
const options = {
collection,
data: { email: args.email, username: args.username },
data: { email: args.email },
req: isolateObjectProperty(context.req, 'transactionID'),
}

View File

@@ -429,24 +429,12 @@ function initCollectionsGraphQL({ config, graphqlResult }: InitCollectionsGraphQ
}
if (!collectionConfig.auth.disableLocalStrategy) {
const authArgs = {}
const canLoginWithEmail =
!collectionConfig.auth.loginWithUsername ||
collectionConfig.auth.loginWithUsername?.allowEmailLogin
const canLoginWithUsername = collectionConfig.auth.loginWithUsername
if (canLoginWithEmail) {
authArgs['email'] = { type: new GraphQLNonNull(GraphQLString) }
}
if (canLoginWithUsername) {
authArgs['username'] = { type: new GraphQLNonNull(GraphQLString) }
}
if (collectionConfig.auth.maxLoginAttempts > 0) {
graphqlResult.Mutation.fields[`unlock${singularName}`] = {
type: new GraphQLNonNull(GraphQLBoolean),
args: authArgs,
args: {
email: { type: new GraphQLNonNull(GraphQLString) },
},
resolve: unlock(collection),
}
}
@@ -467,8 +455,9 @@ function initCollectionsGraphQL({ config, graphqlResult }: InitCollectionsGraphQ
},
}),
args: {
...authArgs,
email: { type: GraphQLString },
password: { type: GraphQLString },
username: { type: GraphQLString },
},
resolve: login(collection),
}
@@ -477,8 +466,8 @@ function initCollectionsGraphQL({ config, graphqlResult }: InitCollectionsGraphQ
type: new GraphQLNonNull(GraphQLBoolean),
args: {
disableEmail: { type: GraphQLBoolean },
email: { type: new GraphQLNonNull(GraphQLString) },
expiration: { type: GraphQLInt },
...authArgs,
},
resolve: forgotPassword(collection),
}

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview-react",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "The official live preview React SDK for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/live-preview",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "The official live preview JavaScript SDK for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/next",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -8,18 +8,9 @@ import { headersWithCors } from '../../../utilities/headersWithCors.js'
export const unlock: CollectionRouteHandler = async ({ collection, req }) => {
const { t } = req
const authData = collection.config.auth?.loginWithUsername
? {
email: typeof req.data?.email === 'string' ? req.data.email : '',
username: typeof req.data?.username === 'string' ? req.data.username : '',
}
: {
email: typeof req.data?.email === 'string' ? req.data.email : '',
}
await unlockOperation({
collection,
data: authData,
data: { email: req.data.email as string },
req,
})

View File

@@ -98,16 +98,14 @@ export const Auth: React.FC<Props> = (props) => {
<div className={[baseClass, className].filter(Boolean).join(' ')}>
{!disableLocalStrategy && (
<React.Fragment>
{(!loginWithUsername ||
loginWithUsername?.allowEmailLogin ||
loginWithUsername?.requireEmail) && (
{!loginWithUsername && (
<EmailField
autoComplete="email"
disabled={disabled}
label={t('general:email')}
name="email"
readOnly={readOnly}
required={!loginWithUsername || loginWithUsername?.requireEmail}
required
/>
)}
{loginWithUsername && (

View File

@@ -5,7 +5,7 @@ export type Props = {
collectionSlug: SanitizedCollectionConfig['slug']
disableLocalStrategy?: boolean
email: string
loginWithUsername: SanitizedCollectionConfig['auth']['loginWithUsername']
loginWithUsername: boolean
operation: 'create' | 'update'
readOnly: boolean
requirePassword?: boolean

View File

@@ -1,109 +0,0 @@
'use client'
import type { PayloadRequest } from 'payload'
import { EmailField, TextField, useConfig, useTranslation } from '@payloadcms/ui'
import { email, username } from 'payload/shared'
import React from 'react'
export type LoginFieldProps = {
type: 'email' | 'emailOrUsername' | 'username'
}
export const LoginField: React.FC<LoginFieldProps> = ({ type }) => {
const { t } = useTranslation()
const config = useConfig()
if (type === 'email') {
return (
<EmailField
autoComplete="email"
label={t('general:email')}
name="email"
required
validate={(value) =>
email(value, {
name: 'email',
type: 'email',
data: {},
preferences: { fields: {} },
req: { t } as PayloadRequest,
required: true,
siblingData: {},
})
}
/>
)
}
if (type === 'username') {
return (
<TextField
label={t('authentication:username')}
name="username"
required
validate={(value) =>
username(value, {
name: 'username',
type: 'text',
data: {},
preferences: { fields: {} },
req: {
payload: {
config,
},
t,
} as PayloadRequest,
required: true,
siblingData: {},
})
}
/>
)
}
if (type === 'emailOrUsername') {
return (
<TextField
label={t('authentication:emailOrUsername')}
name="username"
required
validate={(value) => {
const passesUsername = username(value, {
name: 'username',
type: 'text',
data: {},
preferences: { fields: {} },
req: {
payload: {
config,
},
t,
} as PayloadRequest,
required: true,
siblingData: {},
})
const passesEmail = email(value, {
name: 'username',
type: 'email',
data: {},
preferences: { fields: {} },
req: {
payload: {
config,
},
t,
} as PayloadRequest,
required: true,
siblingData: {},
})
if (!passesEmail && !passesUsername) {
return `${t('general:email')}: ${passesEmail} ${t('general:username')}: ${passesUsername}`
}
return true
}}
/>
)
}
return null
}

View File

@@ -8,12 +8,17 @@ const Link = (LinkImport.default || LinkImport) as unknown as typeof LinkImport.
import type { FormState, PayloadRequest } from 'payload'
import { Form, FormSubmit, PasswordField, useConfig, useTranslation } from '@payloadcms/ui'
import { password } from 'payload/shared'
import {
EmailField,
Form,
FormSubmit,
PasswordField,
TextField,
useConfig,
useTranslation,
} from '@payloadcms/ui'
import { email, password, text } from 'payload/shared'
import type { LoginFieldProps } from '../LoginField/index.js'
import { LoginField } from '../LoginField/index.js'
import './index.scss'
export const LoginForm: React.FC<{
@@ -31,17 +36,7 @@ export const LoginForm: React.FC<{
} = config
const collectionConfig = config.collections?.find((collection) => collection?.slug === userSlug)
const { auth: authOptions } = collectionConfig
const loginWithUsername = authOptions.loginWithUsername
const canLoginWithEmail =
!authOptions.loginWithUsername || authOptions.loginWithUsername.allowEmailLogin
const canLoginWithUsername = authOptions.loginWithUsername
const [loginType] = React.useState<LoginFieldProps['type']>(() => {
if (canLoginWithEmail && canLoginWithUsername) return 'emailOrUsername'
if (canLoginWithUsername) return 'username'
return 'email'
})
const loginWithUsername = collectionConfig?.auth?.loginWithUsername
const { t } = useTranslation()
@@ -80,7 +75,47 @@ export const LoginForm: React.FC<{
waitForAutocomplete
>
<div className={`${baseClass}__inputWrap`}>
<LoginField type={loginType} />
{loginWithUsername ? (
<TextField
label={t('authentication:username')}
name="username"
required
validate={(value) =>
text(value, {
name: 'username',
type: 'text',
data: {},
preferences: { fields: {} },
req: {
payload: {
config,
},
t,
} as PayloadRequest,
required: true,
siblingData: {},
})
}
/>
) : (
<EmailField
autoComplete="email"
label={t('general:email')}
name="email"
required
validate={(value) =>
email(value, {
name: 'email',
type: 'email',
data: {},
preferences: { fields: {} },
req: { t } as PayloadRequest,
required: true,
siblingData: {},
})
}
/>
)}
<PasswordField
autoComplete="off"
label={t('general:password')}

View File

@@ -1,6 +1,6 @@
{
"name": "payload",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "Node, React, Headless CMS and Application Framework built on Next.js",
"keywords": [
"admin panel",

View File

@@ -1,6 +1,6 @@
import type { Field } from '../../fields/config/types.js'
export const accountLockFields: Field[] = [
export default [
{
name: 'loginAttempts',
type: 'number',

View File

@@ -7,7 +7,8 @@ const encryptKey: FieldHook = ({ req, value }) =>
const decryptKey: FieldHook = ({ req, value }) =>
value ? req.payload.decrypt(value as string) : undefined
export const apiKeyFields = [
// eslint-disable-next-line no-restricted-exports
export default [
{
name: 'enableAPIKey',
type: 'checkbox',

View File

@@ -1,6 +1,6 @@
import type { Field } from '../../fields/config/types.js'
export const baseAuthFields: Field[] = [
const baseAuthFields: Field[] = [
{
name: 'resetPasswordToken',
type: 'text',
@@ -22,3 +22,5 @@ export const baseAuthFields: Field[] = [
hidden: true,
},
]
export default baseAuthFields

View File

@@ -1,26 +0,0 @@
import type { Field } from '../../fields/config/types.js'
import { email } from '../../fields/validations.js'
export const emailField = ({ required = true }: { required?: boolean }): Field => ({
name: 'email',
type: 'email',
admin: {
components: {
Field: required ? () => null : undefined,
},
},
hooks: {
beforeChange: [
({ value }) => {
if (value) {
return value.toLowerCase().trim()
}
},
],
},
label: ({ t }) => t('general:email'),
required,
unique: true,
validate: email,
})

View File

@@ -0,0 +1,38 @@
import type { Field } from '../../fields/config/types.js'
import { email } from '../../fields/validations.js'
export default (field: string): Field[] => {
const formattedFields = [
{
name: 'email',
type: 'email',
admin: {
components: {
Field: field === 'username' ? undefined : () => null,
},
},
label: ({ t }) => t('general:email'),
required: true,
unique: field === 'email' ? true : false,
validate: email,
},
] as Field[]
if (field === 'username') {
formattedFields.push({
name: 'username',
type: 'text',
admin: {
components: {
Field: () => null,
},
},
label: ({ t }) => t('authentication:username'),
required: true,
unique: true,
} as Field)
}
return formattedFields
}

View File

@@ -1,26 +0,0 @@
import type { Field } from '../../fields/config/types.js'
import { username } from '../../fields/validations.js'
export const usernameField: Field = {
name: 'username',
type: 'text',
admin: {
components: {
Field: () => null,
},
},
hooks: {
beforeChange: [
({ value }) => {
if (value) {
return value.toLowerCase().trim()
}
},
],
},
label: ({ t }) => t('authentication:username'),
required: true,
unique: true,
validate: username,
}

View File

@@ -15,7 +15,7 @@ const autoRemoveVerificationToken: FieldHook = ({ data, operation, originalDoc,
return value
}
export const verificationFields: Field[] = [
export default [
{
name: '_verified',
type: 'checkbox',

View File

@@ -1,44 +0,0 @@
import type { Field } from '../fields/config/types.js'
import type { IncomingAuthType } from './types.js'
import { accountLockFields } from './baseFields/accountLock.js'
import { apiKeyFields } from './baseFields/apiKey.js'
import { baseAuthFields } from './baseFields/auth.js'
import { emailField } from './baseFields/email.js'
import { usernameField } from './baseFields/username.js'
import { verificationFields } from './baseFields/verification.js'
export const getBaseAuthFields = (authConfig: IncomingAuthType): Field[] => {
const authFields: Field[] = []
if (authConfig.useAPIKey) {
authFields.push(...apiKeyFields)
}
if (!authConfig.disableLocalStrategy) {
const emailFieldIndex = authFields.push(emailField({ required: true })) - 1
if (authConfig.loginWithUsername) {
if (
typeof authConfig.loginWithUsername === 'object' &&
authConfig.loginWithUsername.requireEmail === false
) {
authFields[emailFieldIndex] = emailField({ required: false })
}
authFields.push(usernameField)
}
authFields.push(...baseAuthFields)
if (authConfig.verify) {
authFields.push(...verificationFields)
}
if (authConfig.maxLoginAttempts > 0) {
authFields.push(...accountLockFields)
}
}
return authFields
}

View File

@@ -1,5 +1,2 @@
const isLocked = (date: number): boolean => {
if (!date) return false
return date > Date.now()
}
const isLocked = (date: number): boolean => !!(date && date > Date.now())
export default isLocked

View File

@@ -7,7 +7,7 @@ import type {
Collection,
} from '../../collections/config/types.js'
import type { CollectionSlug } from '../../index.js'
import type { PayloadRequest, Where } from '../../types/index.js'
import type { PayloadRequest } from '../../types/index.js'
import { buildAfterOperation } from '../../collections/operations/utils.js'
import { APIError } from '../../errors/index.js'
@@ -30,20 +30,9 @@ export type Result = string
export const forgotPasswordOperation = async <TSlug extends CollectionSlug>(
incomingArgs: Arguments<TSlug>,
): Promise<null | string> => {
const loginWithUsername = incomingArgs.collection.config.auth.loginWithUsername
const { data } = incomingArgs
const loginWithUsername = incomingArgs.collection?.config?.auth?.loginWithUsername
const canLoginWithUsername = Boolean(loginWithUsername)
const canLoginWithEmail = !loginWithUsername || loginWithUsername.allowEmailLogin
const sanitizedEmail =
(canLoginWithEmail && (incomingArgs.data.email || '').toLowerCase().trim()) || null
const sanitizedUsername =
'username' in data && typeof data?.username === 'string'
? data.username.toLowerCase().trim()
: null
if (!sanitizedEmail && !sanitizedUsername) {
if (!incomingArgs.data.email && !incomingArgs.data.username) {
throw new APIError(
`Missing ${loginWithUsername ? 'username' : 'email'}.`,
httpStatus.BAD_REQUEST,
@@ -96,33 +85,20 @@ export const forgotPasswordOperation = async <TSlug extends CollectionSlug>(
resetPasswordToken?: string
}
if (!sanitizedEmail && !sanitizedUsername) {
if (!data.email && !data.username) {
throw new APIError(
`Missing ${loginWithUsername ? 'username' : 'email'}.`,
httpStatus.BAD_REQUEST,
)
}
let whereConstraint: Where = {}
if (canLoginWithEmail && sanitizedEmail) {
whereConstraint = {
email: {
equals: sanitizedEmail,
},
}
} else if (canLoginWithUsername && sanitizedUsername) {
whereConstraint = {
username: {
equals: sanitizedUsername,
},
}
}
let user = await payload.db.findOne<UserDoc>({
collection: collectionConfig.slug,
req,
where: whereConstraint,
where:
loginWithUsername && data?.username
? { username: { equals: data.username } }
: { email: { equals: data.email.toLowerCase() } },
})
// We don't want to indicate specifically that an email was not found,
@@ -140,7 +116,7 @@ export const forgotPasswordOperation = async <TSlug extends CollectionSlug>(
req,
})
if (!disableEmail && user.email) {
if (!disableEmail) {
const protocol = new URL(req.url).protocol // includes the final :
const serverURL =
config.serverURL !== null && config.serverURL !== ''
@@ -173,7 +149,7 @@ export const forgotPasswordOperation = async <TSlug extends CollectionSlug>(
from: `"${email.defaultFromName}" <${email.defaultFromAddress}>`,
html,
subject,
to: user.email,
to: loginWithUsername ? user.email : data.email,
})
}

View File

@@ -1,26 +1,23 @@
import type {
AuthOperationsFromCollectionSlug,
CollectionSlug,
Payload,
RequestContext,
} from '../../../index.js'
import type { CollectionSlug, Payload, RequestContext } from '../../../index.js'
import type { PayloadRequest } from '../../../types/index.js'
import { APIError } from '../../../errors/index.js'
import { createLocalReq } from '../../../utilities/createLocalReq.js'
import { unlockOperation } from '../unlock.js'
export type Options<TSlug extends CollectionSlug> = {
collection: TSlug
export type Options<T extends CollectionSlug> = {
collection: T
context?: RequestContext
data: AuthOperationsFromCollectionSlug<TSlug>['unlock']
data: {
email
}
overrideAccess: boolean
req?: PayloadRequest
}
async function localUnlock<TSlug extends CollectionSlug>(
async function localUnlock<T extends CollectionSlug>(
payload: Payload,
options: Options<TSlug>,
options: Options<T>,
): Promise<boolean> {
const { collection: collectionSlug, data, overrideAccess = true } = options
@@ -32,7 +29,7 @@ async function localUnlock<TSlug extends CollectionSlug>(
)
}
return unlockOperation<TSlug>({
return unlockOperation({
collection,
data,
overrideAccess,

View File

@@ -6,7 +6,7 @@ import type {
DataFromCollectionSlug,
} from '../../collections/config/types.js'
import type { CollectionSlug } from '../../index.js'
import type { PayloadRequest, Where } from '../../types/index.js'
import type { PayloadRequest } from '../../types/index.js'
import type { User } from '../types.js'
import { buildAfterOperation } from '../../collections/operations/utils.js'
@@ -82,47 +82,26 @@ export const loginOperation = async <TSlug extends CollectionSlug>(
// /////////////////////////////////////
let user
const loginWithUsername = collectionConfig?.auth?.loginWithUsername
const { email: unsanitizedEmail, password } = data
const loginWithUsername = collectionConfig.auth.loginWithUsername
const username = 'username' in data && data.username
const sanitizedEmail =
typeof unsanitizedEmail === 'string' ? unsanitizedEmail.toLowerCase().trim() : null
const sanitizedUsername =
'username' in data && typeof data?.username === 'string'
? data.username.toLowerCase().trim()
: null
const canLoginWithUsername = Boolean(loginWithUsername)
const canLoginWithEmail = !loginWithUsername || loginWithUsername.allowEmailLogin
// cannot login with email, did not provide username
if (!canLoginWithEmail && !sanitizedUsername) {
if (loginWithUsername && !username) {
throw new ValidationError({
collection: collectionConfig.slug,
errors: [{ field: 'username', message: req.i18n.t('validation:required') }],
})
}
// cannot login with username, did not provide email
if (!canLoginWithUsername && !sanitizedEmail) {
if (
!loginWithUsername &&
(typeof unsanitizedEmail !== 'string' || unsanitizedEmail.trim() === '')
) {
throw new ValidationError({
collection: collectionConfig.slug,
errors: [{ field: 'email', message: req.i18n.t('validation:required') }],
})
}
// can login with either email or username, did not provide either
if (!sanitizedUsername && !sanitizedEmail) {
throw new ValidationError({
collection: collectionConfig.slug,
errors: [
{ field: 'email', message: req.i18n.t('validation:required') },
{ field: 'username', message: req.i18n.t('validation:required') },
],
})
}
// did not provide password for login
if (typeof password !== 'string' || password.trim() === '') {
throw new ValidationError({
collection: collectionConfig.slug,
@@ -130,59 +109,22 @@ export const loginOperation = async <TSlug extends CollectionSlug>(
})
}
let whereConstraint: Where = {}
const emailConstraint: Where = {
email: {
equals: sanitizedEmail,
},
}
const usernameConstraint: Where = {
username: {
equals: sanitizedUsername,
},
}
if (canLoginWithEmail && canLoginWithUsername && (sanitizedUsername || sanitizedEmail)) {
if (sanitizedUsername) {
whereConstraint = {
or: [
usernameConstraint,
{
email: {
equals: sanitizedUsername,
},
},
],
}
} else {
whereConstraint = {
or: [
emailConstraint,
{
username: {
equals: sanitizedEmail,
},
},
],
}
}
} else if (canLoginWithEmail && sanitizedEmail) {
whereConstraint = emailConstraint
} else if (canLoginWithUsername && sanitizedUsername) {
whereConstraint = usernameConstraint
}
const email = unsanitizedEmail ? unsanitizedEmail.toLowerCase().trim() : null
user = await payload.db.findOne<any>({
collection: collectionConfig.slug,
req,
where: whereConstraint,
where:
loginWithUsername && username
? { username: { equals: username } }
: { email: { equals: unsanitizedEmail.toLowerCase() } },
})
if (!user || (args.collection.config.auth.verify && user._verified === false)) {
throw new AuthenticationError(req.t, Boolean(canLoginWithUsername && sanitizedUsername))
throw new AuthenticationError(req.t, loginWithUsername)
}
if (user && isLocked(new Date(user.lockUntil).getTime())) {
if (user && isLocked(user.lockUntil)) {
throw new LockedAuth(req.t)
}
@@ -218,7 +160,7 @@ export const loginOperation = async <TSlug extends CollectionSlug>(
const fieldsToSign = getFieldsToSign({
collectionConfig,
email: sanitizedEmail,
email,
user,
})

View File

@@ -120,3 +120,5 @@ export const resetPasswordOperation = async (args: Arguments): Promise<Result> =
throw error
}
}
export default resetPasswordOperation

View File

@@ -1,11 +1,7 @@
import httpStatus from 'http-status'
import type {
AuthOperationsFromCollectionSlug,
Collection,
} from '../../collections/config/types.js'
import type { CollectionSlug } from '../../index.js'
import type { PayloadRequest, Where } from '../../types/index.js'
import type { Collection } from '../../collections/config/types.js'
import type { PayloadRequest } from '../../types/index.js'
import { APIError } from '../../errors/index.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
@@ -14,16 +10,20 @@ import { killTransaction } from '../../utilities/killTransaction.js'
import executeAccess from '../executeAccess.js'
import { resetLoginAttempts } from '../strategies/local/resetLoginAttempts.js'
export type Arguments<TSlug extends CollectionSlug> = {
export type Args = {
collection: Collection
data: AuthOperationsFromCollectionSlug<TSlug>['unlock']
data: {
email: string
}
overrideAccess?: boolean
req: PayloadRequest
}
export const unlockOperation = async <TSlug extends CollectionSlug>(
args: Arguments<TSlug>,
): Promise<boolean> => {
export const unlockOperation = async (args: Args): Promise<boolean> => {
if (!Object.prototype.hasOwnProperty.call(args.data, 'email')) {
throw new APIError('Missing email.', httpStatus.BAD_REQUEST)
}
const {
collection: { config: collectionConfig },
overrideAccess,
@@ -31,25 +31,6 @@ export const unlockOperation = async <TSlug extends CollectionSlug>(
req,
} = args
const loginWithUsername = collectionConfig.auth.loginWithUsername
const canLoginWithUsername = Boolean(loginWithUsername)
const canLoginWithEmail = !loginWithUsername || loginWithUsername.allowEmailLogin
const sanitizedEmail = canLoginWithEmail && (args.data?.email || '').toLowerCase().trim()
const sanitizedUsername =
(canLoginWithUsername &&
'username' in args.data &&
typeof args.data.username === 'string' &&
args.data.username.toLowerCase().trim()) ||
null
if (!sanitizedEmail && !sanitizedUsername) {
throw new APIError(
`Missing ${collectionConfig.auth.loginWithUsername ? 'username' : 'email'}.`,
httpStatus.BAD_REQUEST,
)
}
try {
const shouldCommit = await initTransaction(req)
@@ -61,31 +42,23 @@ export const unlockOperation = async <TSlug extends CollectionSlug>(
await executeAccess({ req }, collectionConfig.access.unlock)
}
const options = { ...args }
const { data } = options
// /////////////////////////////////////
// Unlock
// /////////////////////////////////////
let whereConstraint: Where = {}
if (canLoginWithEmail && sanitizedEmail) {
whereConstraint = {
email: {
equals: sanitizedEmail,
},
}
} else if (canLoginWithUsername && sanitizedUsername) {
whereConstraint = {
username: {
equals: sanitizedUsername,
},
}
if (!data.email) {
throw new APIError('Missing email.', httpStatus.BAD_REQUEST)
}
const user = await req.payload.db.findOne({
collection: collectionConfig.slug,
locale,
req,
where: whereConstraint,
where: { email: { equals: data.email.toLowerCase() } },
})
let result
@@ -110,3 +83,5 @@ export const unlockOperation = async <TSlug extends CollectionSlug>(
throw error
}
}
export default unlockOperation

View File

@@ -118,11 +118,6 @@ export type AuthStrategy = {
name: string
}
export type LoginWithUsernameOptions = {
allowEmailLogin?: boolean
requireEmail?: boolean
}
export interface IncomingAuthType {
cookies?: {
domain?: string
@@ -136,7 +131,7 @@ export interface IncomingAuthType {
generateEmailSubject?: GenerateForgotPasswordEmailSubject
}
lockTime?: number
loginWithUsername?: LoginWithUsernameOptions | boolean
loginWithUsername?: boolean
maxLoginAttempts?: number
removeTokenFromResponses?: true
strategies?: AuthStrategy[]
@@ -155,13 +150,11 @@ export type VerifyConfig = {
generateEmailSubject?: GenerateVerifyEmailSubject
}
export interface Auth
extends Omit<DeepRequired<IncomingAuthType>, 'forgotPassword' | 'loginWithUsername' | 'verify'> {
export interface Auth extends Omit<DeepRequired<IncomingAuthType>, 'forgotPassword' | 'verify'> {
forgotPassword?: {
generateEmailHTML?: GenerateForgotPasswordEmailHTML
generateEmailSubject?: GenerateForgotPasswordEmailSubject
}
loginWithUsername: LoginWithUsernameOptions | false
verify?: VerifyConfig | boolean
}

View File

@@ -1,5 +1,3 @@
import type { LoginWithUsernameOptions } from '../../auth/types.js'
import defaultAccess from '../../auth/defaultAccess.js'
export const defaults = {
@@ -61,8 +59,3 @@ export const authDefaults = {
tokenExpiration: 7200,
verify: false,
}
export const loginWithUsernameDefaults: LoginWithUsernameOptions = {
allowEmailLogin: false,
requireEmail: false,
}

View File

@@ -3,7 +3,11 @@ import merge from 'deepmerge'
import type { Config, SanitizedConfig } from '../../config/types.js'
import type { CollectionConfig, SanitizedCollectionConfig } from './types.js'
import { getBaseAuthFields } from '../../auth/getAuthFields.js'
import baseAccountLockFields from '../../auth/baseFields/accountLock.js'
import baseAPIKeyFields from '../../auth/baseFields/apiKey.js'
import baseAuthFields from '../../auth/baseFields/auth.js'
import baseLoginField from '../../auth/baseFields/loginField.js'
import baseVerificationFields from '../../auth/baseFields/verification.js'
import { TimestampsRequired } from '../../errors/TimestampsRequired.js'
import { sanitizeFields } from '../../fields/config/sanitize.js'
import { fieldAffectsData } from '../../fields/config/types.js'
@@ -13,7 +17,7 @@ import { formatLabels } from '../../utilities/formatLabels.js'
import { isPlainObject } from '../../utilities/isPlainObject.js'
import baseVersionFields from '../../versions/baseFields.js'
import { versionDefaults } from '../../versions/defaults.js'
import { authDefaults, defaults, loginWithUsernameDefaults } from './defaults.js'
import { authDefaults, defaults } from './defaults.js'
export const sanitizeCollection = async (
config: Config,
@@ -137,8 +141,27 @@ export const sanitizeCollection = async (
isMergeableObject: isPlainObject,
})
if (!sanitized.auth.disableLocalStrategy && sanitized.auth.verify === true) {
sanitized.auth.verify = {}
let authFields = []
if (sanitized.auth.useAPIKey) {
authFields = authFields.concat(baseAPIKeyFields)
}
if (!sanitized.auth.disableLocalStrategy) {
const loginField = sanitized.auth.loginWithUsername ? 'username' : 'email'
authFields = authFields.concat(baseLoginField(loginField))
authFields = authFields.concat(baseAuthFields)
if (sanitized.auth.verify) {
if (sanitized.auth.verify === true) sanitized.auth.verify = {}
authFields = authFields.concat(baseVerificationFields)
}
if (sanitized.auth.maxLoginAttempts > 0) {
authFields = authFields.concat(baseAccountLockFields)
}
}
// disable duplicate for auth enabled collections by default
@@ -148,16 +171,7 @@ export const sanitizeCollection = async (
sanitized.auth.strategies = []
}
sanitized.auth.loginWithUsername = sanitized.auth.loginWithUsername
? merge(
loginWithUsernameDefaults,
typeof sanitized.auth.loginWithUsername === 'boolean'
? {}
: sanitized.auth.loginWithUsername,
)
: false
sanitized.fields = mergeBaseFields(sanitized.fields, getBaseAuthFields(sanitized.auth))
sanitized.fields = mergeBaseFields(sanitized.fields, authFields)
}
return sanitized as SanitizedCollectionConfig

View File

@@ -92,13 +92,7 @@ const collectionSchema = joi.object().keys({
generateEmailSubject: joi.func(),
}),
lockTime: joi.number(),
loginWithUsername: joi.alternatives().try(
joi.boolean(),
joi.object().keys({
allowEmailLogin: joi.boolean(),
requireEmail: joi.boolean(),
}),
),
loginWithUsername: joi.boolean(),
maxLoginAttempts: joi.number(),
removeTokenFromResponses: joi.boolean().valid(true),
strategies: joi.array().items(

View File

@@ -214,6 +214,10 @@ export const createOperation = async <TSlug extends CollectionSlug>(
let doc
if (collectionConfig.auth && !collectionConfig.auth.disableLocalStrategy) {
if (data.email) {
resultWithLocales.email = (data.email as string).toLowerCase()
}
if (collectionConfig.auth.verify) {
resultWithLocales._verified = Boolean(resultWithLocales._verified) || false
resultWithLocales._verificationToken = crypto.randomBytes(20).toString('hex')
@@ -256,7 +260,7 @@ export const createOperation = async <TSlug extends CollectionSlug>(
// Send verification email if applicable
// /////////////////////////////////////
if (collectionConfig.auth && collectionConfig.auth.verify && result.email) {
if (collectionConfig.auth && collectionConfig.auth.verify) {
await sendVerificationEmail({
collection: { config: collectionConfig },
config: payload.config,

View File

@@ -126,31 +126,6 @@ export const email: Validate<string, unknown, unknown, EmailField> = (
return true
}
export const username: Validate<string, unknown, unknown, TextField> = (
value,
{
req: {
payload: { config },
t,
},
required,
},
) => {
let maxLength: number
if (typeof config?.defaultMaxTextLength === 'number') maxLength = config.defaultMaxTextLength
if (value && maxLength && value.length > maxLength) {
return t('validation:shorterThanMax', { maxLength })
}
if ((value && !/^[\w.-]+$/.test(value)) || (!value && required)) {
return t('validation:username')
}
return true
}
export const textarea: Validate<string, unknown, unknown, TextareaField> = (
value,
{

View File

@@ -74,14 +74,12 @@ export interface GeneratedTypes {
login: {
email: string
password: string
username?: string
}
registerFirstUser: {
email: string
password: string
}
unlock: {
email: string
}
}
}
collectionsUntyped: {
@@ -351,7 +349,7 @@ export class BasePayload {
options: UnlockOptions<TSlug>,
): Promise<boolean> => {
const { unlock } = localOperations.auth
return unlock<TSlug>(this, options)
return unlock(this, options)
}
updateGlobal = async <TSlug extends GlobalSlug>(
@@ -710,11 +708,11 @@ export type {
AfterErrorHook as CollectionAfterErrorHook,
AfterForgotPasswordHook as CollectionAfterForgotPasswordHook,
AfterLoginHook as CollectionAfterLoginHook,
AfterLogoutHook as CollectionAfterLogoutHook,
AfterMeHook as CollectionAfterMeHook,
AfterLogoutHook,
AfterMeHook,
AfterOperationHook as CollectionAfterOperationHook,
AfterReadHook as CollectionAfterReadHook,
AfterRefreshHook as CollectionAfterRefreshHook,
AfterRefreshHook,
AuthCollection,
AuthOperationsFromCollectionSlug,
BeforeChangeHook as CollectionBeforeChangeHook,

View File

@@ -3,7 +3,6 @@ import type { JSONSchema4, JSONSchema4TypeName } from 'json-schema'
import pluralize from 'pluralize'
const { singular } = pluralize
import type { Auth } from '../auth/types.js'
import type { SanitizedCollectionConfig } from '../collections/config/types.js'
import type { SanitizedConfig } from '../config/types.js'
import type { Field, FieldAffectingData, Option } from '../fields/config/types.js'
@@ -608,69 +607,60 @@ export function entityToJSONSchema(
}
}
const fieldType: JSONSchema4 = {
type: 'string',
required: false,
}
const generateAuthFieldTypes = (
loginWithUsername: Auth['loginWithUsername'],
withPassword = false,
): JSONSchema4 => {
const passwordField = {
password: fieldType,
function generateOperationJSONSchema(
config: SanitizedCollectionConfig,
operation: 'forgotPassword' | 'login' | 'registerFirstUser',
): JSONSchema4 {
const usernameLogin = config.auth?.loginWithUsername
const fieldType: JSONSchema4 = {
type: 'string',
}
if (loginWithUsername) {
if (loginWithUsername.allowEmailLogin) {
return {
additionalProperties: false,
oneOf: [
{
additionalProperties: false,
properties: { email: fieldType, ...(withPassword ? { password: fieldType } : {}) },
required: ['email', ...(withPassword ? ['password'] : [])],
},
{
additionalProperties: false,
properties: { username: fieldType, ...(withPassword ? { password: fieldType } : {}) },
required: ['username', ...(withPassword ? ['password'] : [])],
},
],
let properties: JSONSchema4['properties'] = {}
switch (operation) {
case 'login': {
properties = {
password: fieldType,
[usernameLogin ? 'username' : 'email']: fieldType,
}
break
}
return {
additionalProperties: false,
properties: {
username: fieldType,
...(withPassword ? { password: fieldType } : {}),
},
required: ['username', ...(withPassword ? ['password'] : [])],
case 'forgotPassword': {
properties = {
[usernameLogin ? 'username' : 'email']: fieldType,
}
break
}
case 'registerFirstUser': {
properties = {
email: fieldType,
password: fieldType,
}
if (usernameLogin) properties.username = fieldType
break
}
}
return {
additionalProperties: false,
properties: {
email: fieldType,
...(withPassword ? { password: fieldType } : {}),
},
required: ['email', ...(withPassword ? ['password'] : [])],
properties,
required: Object.keys(properties),
}
}
export function authCollectionToOperationsJSONSchema(
config: SanitizedCollectionConfig,
): JSONSchema4 {
const loginWithUsername = config.auth?.loginWithUsername
const generatedFields: JSONSchema4 = generateAuthFieldTypes(loginWithUsername)
const generatedFieldsWithPassword: JSONSchema4 = generateAuthFieldTypes(loginWithUsername, true)
const properties: JSONSchema4['properties'] = {
forgotPassword: generatedFields,
login: generatedFieldsWithPassword,
registerFirstUser: generatedFieldsWithPassword,
unlock: generatedFields,
const properties = {
forgotPassword: {
...generateOperationJSONSchema(config, 'forgotPassword'),
},
login: {
...generateOperationJSONSchema(config, 'login'),
},
registerFirstUser: {
...generateOperationJSONSchema(config, 'registerFirstUser'),
},
}
return {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-cloud-storage",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "The official cloud storage plugin for Payload CMS",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-cloud",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "The official Payload Cloud plugin",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-form-builder",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "Form builder plugin for Payload CMS",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-nested-docs",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "The official Nested Docs plugin for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-redirects",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "Redirects plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-relationship-object-ids",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "A Payload plugin to store all relationship IDs as ObjectIDs",
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-search",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "Search plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-seo",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "SEO plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/plugin-stripe",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "Stripe plugin for Payload",
"keywords": [
"payload",

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-lexical",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "The officially supported Lexical richtext adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/richtext-slate",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "The officially supported Slate richtext adapter for Payload",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-azure",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "Payload storage adapter for Azure Blob Storage",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-gcs",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "Payload storage adapter for Google Cloud Storage",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-s3",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "Payload storage adapter for Amazon S3",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-uploadthing",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "Payload storage adapter for uploadthing",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/storage-vercel-blob",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"description": "Payload storage adapter for Vercel Blob Storage",
"homepage": "https://payloadcms.com",
"repository": {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/translations",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -30,12 +30,8 @@ export async function translateText(text: string, targetLang: string) {
})
try {
const data = await response.json()
if (data?.choices?.[0]) {
console.log(' Old text:', text, 'New text:', data.choices[0].message.content.trim())
return data.choices[0].message.content.trim()
} else {
console.log(`Could not translate: ${text} in lang: ${targetLang}`)
}
console.log(' Old text:', text, 'New text:', data.choices[0].message.content.trim())
return data.choices[0].message.content.trim()
} catch (e) {
console.error('Error translating:', text, 'to', targetLang, 'response', response, '. Error:', e)
throw e

View File

@@ -21,7 +21,6 @@ export const clientTranslationKeys = createClientTranslationKeys([
'authentication:createFirstUser',
'authentication:emailNotValid',
'authentication:usernameNotValid',
'authentication:emailOrUsername',
'authentication:emailSent',
'authentication:emailVerified',
'authentication:enableAPIKey',
@@ -231,7 +230,6 @@ export const clientTranslationKeys = createClientTranslationKeys([
'general:true',
'general:users',
'general:user',
'general:username',
'general:unauthorized',
'general:unsavedChangesDuplicate',
'general:untitled',
@@ -283,7 +281,6 @@ export const clientTranslationKeys = createClientTranslationKeys([
'validation:required',
'validation:requiresAtLeast',
'validation:shorterThanMax',
'validation:username',
'version:aboutToPublishSelection',
'version:aboutToRestore',

View File

@@ -18,7 +18,6 @@ export const arTranslations: DefaultTranslationsObject = {
confirmPassword: 'تأكيد كلمة المرور',
createFirstUser: 'إنشاء المستخدم الأوّل',
emailNotValid: 'البريد الإلكتروني غير صالح',
emailOrUsername: 'البريد الإلكتروني أو اسم المستخدم',
emailSent: 'تمّ ارسال البريد الإلكتروني',
emailVerified: 'تم التحقق من البريد الإلكتروني بنجاح.',
enableAPIKey: 'تفعيل مفتاح API',
@@ -295,7 +294,6 @@ export const arTranslations: DefaultTranslationsObject = {
updating: 'جار التحديث',
uploading: 'جار الرفع',
user: 'المستخدم',
username: 'اسم المستخدم',
users: 'المستخدمين',
value: 'القيمة',
welcome: 'مرحبًا',
@@ -358,8 +356,6 @@ export const arTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'هذا الحقل يتطلب رقمين.',
shorterThanMax: 'يجب أن تكون هذه القيمة أقصر من الحد الأقصى للطول الذي هو {{maxLength}} أحرف.',
trueOrFalse: 'يمكن أن يكون هذا الحقل مساويًا فقط للقيمتين صحيح أو خطأ.',
username:
'يرجى إدخال اسم مستخدم صالح. يمكن أن يحتوي على أحرف، أرقام، شرطات، فواصل وشرطات سفلية.',
validUploadID: 'هذا الحقل ليس معرّف تحميل صالح.',
},
version: {

View File

@@ -18,7 +18,6 @@ export const azTranslations: DefaultTranslationsObject = {
confirmPassword: 'Şifrəni təsdiq et',
createFirstUser: 'İlk istifadəçini yaradın',
emailNotValid: 'Təqdim olunan e-poçt etibarlı deyil',
emailOrUsername: 'E-poçt və ya İstifadəçi adı',
emailSent: 'E-poçt göndərildi',
emailVerified: 'Email uğurla təsdiqləndi.',
enableAPIKey: 'API açarını aktivləşdir',
@@ -298,7 +297,6 @@ export const azTranslations: DefaultTranslationsObject = {
updating: 'Yenilənir',
uploading: 'Yüklənir',
user: 'İstifadəçi',
username: 'İstifadəçi adı',
users: 'İstifadəçilər',
value: 'Dəyər',
welcome: 'Xoş gəldiniz',
@@ -363,8 +361,6 @@ export const azTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'Bu sahə iki nömrə tələb edir.',
shorterThanMax: 'Bu dəyər {{maxLength}} simvoldan qısa olmalıdır.',
trueOrFalse: 'Bu sahə yalnız doğru və ya yanlış ola bilər.',
username:
'Zəhmət olmasa, etibarlı bir istifadəçi adı daxil edin. Hərflər, rəqəmlər, tire, nöqtə və alt xəttlər ola bilər.',
validUploadID: 'Bu sahə doğru yükləmə ID-si deyil.',
},
version: {

View File

@@ -18,7 +18,6 @@ export const bgTranslations: DefaultTranslationsObject = {
confirmPassword: 'Потвърди парола',
createFirstUser: 'Създай първи потребител',
emailNotValid: 'Даденият имейл не е валиден',
emailOrUsername: 'Имейл или Потребителско име',
emailSent: 'Имейлът е изпратен',
emailVerified: 'Успешно потвърден имейл.',
enableAPIKey: 'Активирай API ключ',
@@ -296,7 +295,6 @@ export const bgTranslations: DefaultTranslationsObject = {
updating: 'Обновява се',
uploading: 'Качва се',
user: 'Потребител',
username: 'Потребителско име',
users: 'Потребители',
value: 'Стойност',
welcome: 'Добре дошъл',
@@ -363,8 +361,6 @@ export const bgTranslations: DefaultTranslationsObject = {
shorterThanMax:
'Тази стойност трябва да е по-малка от максималната стойност от {{maxLength}} символа.',
trueOrFalse: 'Това поле може да бъде само "true" или "false".',
username:
'Моля, въведете валидно потребителско име. Може да съдържа букви, цифри, тирета, точки и долни черти.',
validUploadID: 'Това поле не е валиден идентификатор на качването.',
},
version: {

View File

@@ -18,7 +18,6 @@ export const csTranslations: DefaultTranslationsObject = {
confirmPassword: 'Potvrdit heslo',
createFirstUser: 'Vytvořit prvního uživatele',
emailNotValid: 'Zadaný email není platný',
emailOrUsername: 'E-mail nebo Uživatelské jméno',
emailSent: 'Email odeslán',
emailVerified: 'E-mail úspěšně ověřen.',
enableAPIKey: 'Povolit API klíč',
@@ -296,7 +295,6 @@ export const csTranslations: DefaultTranslationsObject = {
updating: 'Aktualizace',
uploading: 'Nahrávání',
user: 'Uživatel',
username: 'Uživatelské jméno',
users: 'Uživatelé',
value: 'Hodnota',
welcome: 'Vítejte',
@@ -361,8 +359,6 @@ export const csTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'Toto pole vyžaduje dvě čísla.',
shorterThanMax: 'Tato hodnota musí být kratší než maximální délka {{maxLength}} znaků.',
trueOrFalse: 'Toto pole může být rovno pouze true nebo false.',
username:
'Prosím, zadejte platné uživatelské jméno. Může obsahovat písmena, čísla, pomlčky, tečky a podtržítka.',
validUploadID: 'Toto pole není platné ID pro odeslání.',
},
version: {

View File

@@ -18,7 +18,6 @@ export const deTranslations: DefaultTranslationsObject = {
confirmPassword: 'Passwort bestätigen',
createFirstUser: 'Ersten Benutzer erstellen',
emailNotValid: 'Die angegebene E-Mail-Adresse ist ungültig',
emailOrUsername: 'E-Mail oder Benutzername',
emailSent: 'E-Mail verschickt',
emailVerified: 'E-Mail erfolgreich verifiziert.',
enableAPIKey: 'API-Key aktivieren',
@@ -302,7 +301,6 @@ export const deTranslations: DefaultTranslationsObject = {
updating: 'Aktualisierung',
uploading: 'Hochladen',
user: 'Benutzer',
username: 'Benutzername',
users: 'Benutzer',
value: 'Wert',
welcome: 'Willkommen',
@@ -367,8 +365,6 @@ export const deTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'Dieses Feld muss zwei Nummern enthalten.',
shorterThanMax: 'Dieser Wert muss kürzer als die maximale Länge von {{maxLength}} sein.',
trueOrFalse: 'Dieses Feld kann nur wahr oder falsch sein.',
username:
'Bitte geben Sie einen gültigen Benutzernamen ein. Dieser kann Buchstaben, Zahlen, Bindestriche, Punkte und Unterstriche enthalten.',
validUploadID: "'Dieses Feld enthält keine valide Upload-ID.'",
},
version: {

View File

@@ -18,7 +18,6 @@ export const enTranslations = {
confirmPassword: 'Confirm Password',
createFirstUser: 'Create first user',
emailNotValid: 'The email provided is not valid',
emailOrUsername: 'Email or Username',
emailSent: 'Email Sent',
emailVerified: 'Email verified successfully.',
enableAPIKey: 'Enable API Key',
@@ -299,7 +298,6 @@ export const enTranslations = {
updating: 'Updating',
uploading: 'Uploading',
user: 'User',
username: 'Username',
users: 'Users',
value: 'Value',
welcome: 'Welcome',
@@ -364,8 +362,6 @@ export const enTranslations = {
requiresTwoNumbers: 'This field requires two numbers.',
shorterThanMax: 'This value must be shorter than the max length of {{maxLength}} characters.',
trueOrFalse: 'This field can only be equal to true or false.',
username:
'Please enter a valid username. Can contain letters, numbers, hyphens, periods and underscores.',
validUploadID: 'This field is not a valid upload ID.',
},
version: {

View File

@@ -18,7 +18,6 @@ export const esTranslations: DefaultTranslationsObject = {
confirmPassword: 'Confirmar Contraseña',
createFirstUser: 'Crear al primer usuario',
emailNotValid: 'El correo proporcionado es inválido',
emailOrUsername: 'Correo electrónico o nombre de usuario',
emailSent: 'Correo Enviado',
emailVerified: 'Correo electrónico verificado con éxito.',
enableAPIKey: 'Habilitar Clave API',
@@ -301,7 +300,6 @@ export const esTranslations: DefaultTranslationsObject = {
updating: 'Actualizando',
uploading: 'Subiendo',
user: 'Usuario',
username: 'Nombre de usuario',
users: 'Usuarios',
value: 'Valor',
welcome: 'Bienvenido',
@@ -366,8 +364,6 @@ export const esTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'Este campo requiere dos números.',
shorterThanMax: 'Este dato debe ser más corto que el máximo de {{maxLength}} caracteres.',
trueOrFalse: 'Este campo solamente puede ser verdadero o falso.',
username:
'Por favor, introduzca un nombre de usuario válido. Puede contener letras, números, guiones, puntos y guiones bajos.',
validUploadID: "'Este campo no es una ID de subida válida.'",
},
version: {

View File

@@ -18,7 +18,6 @@ export const faTranslations: DefaultTranslationsObject = {
confirmPassword: 'تأیید گذرواژه',
createFirstUser: 'ایجاد کاربر نخست',
emailNotValid: 'رایانامه ارائه‌شده درست نیست',
emailOrUsername: 'ایمیل یا نام کاربری',
emailSent: 'رایانامه فرستاده شد',
emailVerified: 'ایمیل با موفقیت تایید شد.',
enableAPIKey: 'فعال‌سازی کلید اِی‌پی‌آی',
@@ -296,7 +295,6 @@ export const faTranslations: DefaultTranslationsObject = {
updating: 'در حال به‌روزرسانی',
uploading: 'در حال بارگذاری',
user: 'کاربر',
username: 'نام کاربری',
users: 'کاربران',
value: 'مقدار',
welcome: 'خوش‌آمدید',
@@ -361,8 +359,6 @@ export const faTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'این کادر به دو عدد نیاز دارد.',
shorterThanMax: 'ورودی باید کمتر از {{maxLength}} واژه باشد.',
trueOrFalse: 'این کادر فقط می تواند به صورت true یا false باشد.',
username:
'لطفاً یک نام کاربری معتبر وارد کنید. می تواند شامل حروف، اعداد، خط فاصله، نقاط و خط زیر باشد.',
validUploadID: 'این فیلد یک شناسه بارگذاری معتبر نیست.',
},
version: {

View File

@@ -18,7 +18,6 @@ export const frTranslations: DefaultTranslationsObject = {
confirmPassword: 'Confirmez le mot de passe',
createFirstUser: 'Créer le premier utilisateur',
emailNotValid: 'Ladresse e-mail fournie nest pas valide',
emailOrUsername: "Email ou Nom d'utilisateur",
emailSent: 'E-mail envoyé',
emailVerified: 'E-mail vérifié avec succès.',
enableAPIKey: 'Activer la clé API',
@@ -305,7 +304,6 @@ export const frTranslations: DefaultTranslationsObject = {
updating: 'Mise à jour',
uploading: 'Téléchargement',
user: 'Utilisateur',
username: "Nom d'utilisateur",
users: 'Utilisateurs',
value: 'Valeur',
welcome: 'Bienvenue',
@@ -372,8 +370,6 @@ export const frTranslations: DefaultTranslationsObject = {
shorterThanMax:
'Cette valeur doit être inférieure à la longueur maximale de {{maxLength}} caractères.',
trueOrFalse: 'Ce champ ne peut être égal quà vrai ou faux.',
username:
"Veuillez entrer un nom d'utilisateur valide. Il peut contenir des lettres, des chiffres, des tirets, des points et des tirets bas.",
validUploadID: 'Ce champ nest pas un valide identifiant de fichier.',
},
version: {

View File

@@ -17,7 +17,6 @@ export const heTranslations: DefaultTranslationsObject = {
confirmPassword: 'אישור סיסמה',
createFirstUser: 'יצירת משתמש ראשון',
emailNotValid: 'הדוא"ל שסופק אינו תקין',
emailOrUsername: 'דוא"ל או שם משתמש',
emailSent: 'הודעת דואר נשלחה',
emailVerified: 'דוא"ל אומת בהצלחה.',
enableAPIKey: 'הפעלת מפתח API',
@@ -291,7 +290,6 @@ export const heTranslations: DefaultTranslationsObject = {
updating: 'מעדכן',
uploading: 'מעלה',
user: 'משתמש',
username: 'שם משתמש',
users: 'משתמשים',
value: 'ערך',
welcome: 'ברוך הבא',
@@ -354,7 +352,6 @@ export const heTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'שדה זה דורש שני מספרים.',
shorterThanMax: 'ערך זה חייב להיות קצר מ-{{maxLength}} תווים.',
trueOrFalse: 'שדה זה יכול להיות רק true או false.',
username: 'אנא הזן שם משתמש חוקי. יכול להכיל אותיות, מספרים, מקפים, נקודות וקווים תחתונים.',
validUploadID: 'שדה זה אינו מזהה העלאה תקני.',
},
version: {

View File

@@ -18,7 +18,6 @@ export const hrTranslations: DefaultTranslationsObject = {
confirmPassword: 'Potvrdi lozinku',
createFirstUser: 'Kreiraj prvog korisnika',
emailNotValid: 'Email nije ispravan',
emailOrUsername: 'E-mail ili Korisničko ime',
emailSent: 'Email poslan',
emailVerified: 'Email uspješno provjeren.',
enableAPIKey: 'Omogući API ključ',
@@ -297,7 +296,6 @@ export const hrTranslations: DefaultTranslationsObject = {
updating: 'Ažuriranje',
uploading: 'Prijenos',
user: 'Korisnik',
username: 'Korisničko ime',
users: 'Korisnici',
value: 'Attribute',
welcome: 'Dobrodošli',
@@ -362,8 +360,6 @@ export const hrTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'Ovo polje zahtjeva dva broja.',
shorterThanMax: 'Ova vrijednost mora biti kraća od maksimalne dužine od {{maxLength}} znakova',
trueOrFalse: 'Ovo polje može biti samo točno ili netočno',
username:
'Unesite važeće korisničko ime. Može sadržavati slova, brojeve, crtice, točke i donje crte.',
validUploadID: 'Ovo polje nije valjani ID prijenosa.',
},
version: {

View File

@@ -18,7 +18,6 @@ export const huTranslations: DefaultTranslationsObject = {
confirmPassword: 'Jelszó megerősítése',
createFirstUser: 'Első felhasználó létrehozása',
emailNotValid: 'A megadott e-mail cím érvénytelen',
emailOrUsername: 'E-mail vagy Felhasználónév',
emailSent: 'E-mail elküldve',
emailVerified: 'Az email sikeresen megerősítve.',
enableAPIKey: 'API-kulcs engedélyezése',
@@ -299,7 +298,6 @@ export const huTranslations: DefaultTranslationsObject = {
updating: 'Frissítés',
uploading: 'Feltöltés',
user: 'Felhasználó',
username: 'Felhasználónév',
users: 'Felhasználók',
value: 'Érték',
welcome: 'Üdvözöljük',
@@ -366,8 +364,6 @@ export const huTranslations: DefaultTranslationsObject = {
shorterThanMax:
'Ennek az értéknek rövidebbnek kell lennie, mint a maximálisan megengedett {{maxLength}} karakter.',
trueOrFalse: 'Ez a mező csak igaz vagy hamis lehet.',
username:
'Adjon meg egy érvényes felhasználónevet. Tartalmazhat betűket, számokat, kötőjeleket, pontokat és aláhúzásokat.',
validUploadID: 'Ez a mező nem érvényes feltöltési azonosító.',
},
version: {

View File

@@ -18,7 +18,6 @@ export const itTranslations: DefaultTranslationsObject = {
confirmPassword: 'Conferma Password',
createFirstUser: 'Crea il primo utente',
emailNotValid: "L'email fornita non è valida",
emailOrUsername: 'Email o Nome utente',
emailSent: 'Email Inviata',
emailVerified: 'Email verificata con successo.',
enableAPIKey: 'Abilita la Chiave API',
@@ -299,7 +298,6 @@ export const itTranslations: DefaultTranslationsObject = {
updating: 'Aggiornamento',
uploading: 'Caricamento',
user: 'Utente',
username: 'Nome utente',
users: 'Utenti',
value: 'Valore',
welcome: 'Benvenuto',
@@ -366,8 +364,6 @@ export const itTranslations: DefaultTranslationsObject = {
shorterThanMax:
'Questo valore deve essere inferiore alla lunghezza massima di {{maxLength}} caratteri.',
trueOrFalse: "Questo campo può essere solo uguale a 'true' o 'false'.",
username:
'Inserisci un nome utente valido. Può contenere lettere, numeri, trattini, punti e underscore.',
validUploadID: "'Questo campo non è un ID di Upload valido.'",
},
version: {

View File

@@ -18,7 +18,6 @@ export const jaTranslations: DefaultTranslationsObject = {
confirmPassword: 'パスワードの確認',
createFirstUser: '最初のユーザーを作成',
emailNotValid: '入力されたメールアドレスは無効です。',
emailOrUsername: 'メールまたはユーザー名',
emailSent: 'Emailが送信されました。',
emailVerified: 'メールが正常に確認されました。',
enableAPIKey: 'API Keyを許可',
@@ -297,7 +296,6 @@ export const jaTranslations: DefaultTranslationsObject = {
updating: '更新中',
uploading: 'アップロード中',
user: 'ユーザー',
username: 'ユーザーネーム',
users: 'ユーザー',
value: '値',
welcome: 'ようこそ',
@@ -361,8 +359,6 @@ export const jaTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: '2つの数値が必要です。',
shorterThanMax: '{{maxLength}} 文字以下にする必要があります。',
trueOrFalse: '"true" または "false" の値にする必要があります。',
username:
'有効なユーザーネームを入力してください。文字、数字、ハイフン、ピリオド、アンダースコアを使用できます。',
validUploadID: '有効なアップロードIDではありません。',
},
version: {

View File

@@ -18,7 +18,6 @@ export const koTranslations: DefaultTranslationsObject = {
confirmPassword: '비밀번호 확인',
createFirstUser: '첫 번째 사용자 생성',
emailNotValid: '입력한 이메일은 유효하지 않습니다.',
emailOrUsername: '이메일 또는 사용자 이름',
emailSent: '이메일 전송됨',
emailVerified: '이메일이 성공적으로 인증되었습니다.',
enableAPIKey: 'API 키 활성화',
@@ -296,7 +295,6 @@ export const koTranslations: DefaultTranslationsObject = {
updating: '업데이트 중',
uploading: '업로드 중',
user: '사용자',
username: '사용자 이름',
users: '사용자',
value: '값',
welcome: '환영합니다',
@@ -360,8 +358,6 @@ export const koTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: '이 입력란은 두 개의 숫자가 필요합니다.',
shorterThanMax: '이 값은 최대 길이인 {{maxLength}}자보다 짧아야 합니다.',
trueOrFalse: '이 입력란은 true 또는 false만 가능합니다.',
username:
'유효한 사용자 이름을 입력해 주세요. 글자, 숫자, 하이픈, 마침표, 및 밑줄을 사용할 수 있습니다.',
validUploadID: '이 입력란은 유효한 업로드 ID가 아닙니다.',
},
version: {

View File

@@ -18,7 +18,6 @@ export const myTranslations: DefaultTranslationsObject = {
confirmPassword: 'စကားဝှက်အား ထပ်မံ ရိုက်ထည့်ပါ။',
createFirstUser: 'ပထမဆုံး အသုံးပြုသူကို ဖန်တီးပါ။',
emailNotValid: 'ထည့်သွင်းထားသော အီးမေလ်မှာ မှားယွင်းနေပါသည်။',
emailOrUsername: 'E-mel atau Nama Pengguna',
emailSent: 'မေးလ် ပို့ထားပါသည်။',
emailVerified: 'အီးမေးလ်အတည်ပြုခဲ့ပါပြီ။',
enableAPIKey: 'API Key ကိုဖွင့်ရန်',
@@ -300,7 +299,6 @@ export const myTranslations: DefaultTranslationsObject = {
updating: 'ပြင်ဆင်ရန်',
uploading: 'တင်ပေးနေသည်',
user: 'အသုံးပြုသူ',
username: 'Nama pengguna',
users: 'အသုံးပြုသူများ',
value: 'တန်ဖိုး',
welcome: 'ကြိုဆိုပါတယ်။',
@@ -368,8 +366,6 @@ export const myTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'ဤအကွက်သည် နံပါတ်နှစ်ခု လိုအပ်ပါသည်။',
shorterThanMax: 'ဤတန်ဖိုးသည် စာလုံး {{maxLength}} လုံး၏ အမြင့်ဆုံးအရှည်ထက် ပိုတိုရပါမည်။',
trueOrFalse: 'ဤအကွက်သည် တစ်ခုခုဖြစ်ရပါမည်။',
username:
'Sila masukkan nama pengguna yang sah. Boleh mengandungi huruf, nombor, tanda hubung, titik dan garis bawah.',
validUploadID: "'ဤအကွက်သည် မှန်ကန်သော အပ်လုဒ် ID မဟုတ်ပါ။'",
},
version: {

View File

@@ -18,7 +18,6 @@ export const nbTranslations: DefaultTranslationsObject = {
confirmPassword: 'Bekreft passord',
createFirstUser: 'Opprett første bruker',
emailNotValid: 'E-posten er ikke gyldig',
emailOrUsername: 'E-post eller brukernavn',
emailSent: 'E-post sendt',
emailVerified: 'E-post bekreftet med hell.',
enableAPIKey: 'Aktiver API-nøkkel',
@@ -297,7 +296,6 @@ export const nbTranslations: DefaultTranslationsObject = {
updating: 'Oppdatering',
uploading: 'Opplasting',
user: 'Bruker',
username: 'Brukernavn',
users: 'Brukere',
value: 'Verdi',
welcome: 'Velkommen',
@@ -362,8 +360,6 @@ export const nbTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'Dette feltet krever to tall.',
shorterThanMax: 'Denne verdien må være kortere enn maksimal lengde på {{maxLength}} tegn.',
trueOrFalse: 'Dette feltet kan bare være likt true eller false.',
username:
'Vennligst oppgi et gyldig brukernavn. Kan inneholde bokstaver, nummer, bindestreker, punktum og understrek.',
validUploadID: 'Dette feltet er ikke en gyldig opplastings-ID.',
},
version: {

View File

@@ -18,7 +18,6 @@ export const nlTranslations: DefaultTranslationsObject = {
confirmPassword: 'Wachtwoord bevestigen',
createFirstUser: 'Eerste gebruiker aanmaken',
emailNotValid: 'Het ingevoerde e-mailadres is niet geldig',
emailOrUsername: 'E-mail of Gebruikersnaam',
emailSent: 'E-mail verzonden',
emailVerified: 'E-mail succesvol geverifieerd.',
enableAPIKey: 'Activeer API-sleutel',
@@ -299,7 +298,6 @@ export const nlTranslations: DefaultTranslationsObject = {
updating: 'Bijwerken',
uploading: 'Uploaden',
user: 'Gebruiker',
username: 'Gebruikersnaam',
users: 'Gebruikers',
value: 'Waarde',
welcome: 'Welkom',
@@ -364,8 +362,6 @@ export const nlTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'Dit veld vereist twee nummers.',
shorterThanMax: 'Dit veld moet korter zijn dan de maximale lengte van {{maxLength}} tekens.',
trueOrFalse: 'Dit veld kan alleen waar of onwaar zijn.',
username:
'Voer een geldige gebruikersnaam in. Kan letters, cijfers, koppeltekens, punten en underscores bevatten.',
validUploadID: 'Dit veld is geen geldige upload-ID.',
},
version: {

View File

@@ -18,7 +18,6 @@ export const plTranslations: DefaultTranslationsObject = {
confirmPassword: 'Potwierdź hasło',
createFirstUser: 'Utwórz pierwszego użytkownika',
emailNotValid: 'Podany email jest nieprawidłowy',
emailOrUsername: 'Email lub Nazwa użytkownika',
emailSent: 'Wysłano email',
emailVerified: 'Email zweryfikowany pomyślnie.',
enableAPIKey: 'Aktywuj klucz API',
@@ -297,7 +296,6 @@ export const plTranslations: DefaultTranslationsObject = {
updating: 'Aktualizacja',
uploading: 'Przesyłanie',
user: 'użytkownik',
username: 'Nazwa użytkownika',
users: 'użytkownicy',
value: 'Wartość',
welcome: 'Witaj',
@@ -362,8 +360,6 @@ export const plTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'To pole wymaga dwóch liczb.',
shorterThanMax: 'Ta wartość musi być krótsza niż maksymalna długość znaków: {{maxLength}}.',
trueOrFalse: "To pole może mieć wartość tylko 'true' lub 'false'.",
username:
'Proszę wprowadzić prawidłową nazwę użytkownika. Może zawierać litery, cyfry, myślniki, kropki i podkreślniki.',
validUploadID: 'To pole nie jest prawidłowym identyfikatorem przesyłania.',
},
version: {

View File

@@ -18,7 +18,6 @@ export const ptTranslations: DefaultTranslationsObject = {
confirmPassword: 'Confirmar Senha',
createFirstUser: 'Criar primeiro usuário',
emailNotValid: 'O email fornecido não é válido',
emailOrUsername: 'Email ou Nome de Usuário',
emailSent: 'Email Enviado',
emailVerified: 'Email verificado com sucesso.',
enableAPIKey: 'Habilitar Chave API',
@@ -298,7 +297,6 @@ export const ptTranslations: DefaultTranslationsObject = {
updating: 'Atualizando',
uploading: 'Fazendo upload',
user: 'usuário',
username: 'Nome de usuário',
users: 'usuários',
value: 'Valor',
welcome: 'Boas vindas',
@@ -363,8 +361,6 @@ export const ptTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'Esse campo requer dois números.',
shorterThanMax: 'Esse valor deve ser menor do que o máximo de {{maxLength}} caracteres.',
trueOrFalse: 'Esse campo pode ser apenas verdadeiro (true) ou falso (false)',
username:
'Por favor, insira um nome de usuário válido. Pode conter letras, números, hifens, pontos e sublinhados.',
validUploadID: "'Esse campo não é um ID de upload válido.'",
},
version: {

View File

@@ -18,7 +18,6 @@ export const roTranslations: DefaultTranslationsObject = {
confirmPassword: 'Confirmați parola',
createFirstUser: 'Creați primul utilizator',
emailNotValid: 'Emailul furnizat nu este valid',
emailOrUsername: 'Email sau Nume de utilizator',
emailSent: 'Email trimis',
emailVerified: 'E-mail verificat cu succes.',
enableAPIKey: 'Activați cheia API',
@@ -301,7 +300,6 @@ export const roTranslations: DefaultTranslationsObject = {
updating: 'Actualizare',
uploading: 'Încărcare',
user: 'Utilizator',
username: 'Nume de utilizator',
users: 'Utilizatori',
value: 'Valoare',
welcome: 'Bine ați venit',
@@ -370,8 +368,6 @@ export const roTranslations: DefaultTranslationsObject = {
shorterThanMax:
'Această valoare trebuie să fie mai scurtă decât lungimea maximă de {{maxLength}} caractere.',
trueOrFalse: 'Acest câmp poate fi doar egal cu true sau false.',
username:
'Vă rugăm să introduceți un nume de utilizator valid. Poate conține litere, numere, cratime, puncte și sublinieri.',
validUploadID: 'Acest câmp nu este un ID de încărcare valid.',
},
version: {

View File

@@ -17,7 +17,6 @@ export const rsTranslations: DefaultTranslationsObject = {
confirmPassword: 'Потврди лозинку',
createFirstUser: 'Креирај првог корисника',
emailNotValid: 'Адреса е-поште није валидна',
emailOrUsername: 'Email ili Korisničko ime',
emailSent: 'Порука е-поште прослеђена',
emailVerified: 'Uspešno verifikovan email.',
enableAPIKey: 'Омогући API кључ',
@@ -296,7 +295,6 @@ export const rsTranslations: DefaultTranslationsObject = {
updating: 'Ажурирање',
uploading: 'Пренос',
user: 'Корисник',
username: 'Korisničko ime',
users: 'Корисници',
value: 'Вредност',
welcome: 'Добродошли',
@@ -361,8 +359,6 @@ export const rsTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'Ово поље захтева два броја.',
shorterThanMax: 'Ова вредност мора бити краћа од максималне дужине од {{maxLength}} карактера',
trueOrFalse: 'Ово поље може бити само тачно или нетачно',
username:
'Molimo unesite važeće korisničko ime. Može sadržati slova, brojeve, crtice, tačke i donje crte.',
validUploadID: 'Ово поље не садржи валидан ИД преноса.',
},
version: {

View File

@@ -17,7 +17,6 @@ export const rsLatinTranslations: DefaultTranslationsObject = {
confirmPassword: 'Potvrdi lozinku',
createFirstUser: 'Kreiraj prvog korisnika',
emailNotValid: 'Adresa e-pošte nije validna',
emailOrUsername: 'Email ili Korisničko ime',
emailSent: 'Poruka e-pošte prosleđena',
emailVerified: 'E-pošta je uspešno verifikovana.',
enableAPIKey: 'Omogući API ključ',
@@ -296,7 +295,6 @@ export const rsLatinTranslations: DefaultTranslationsObject = {
updating: 'Ažuriranje',
uploading: 'Prenos',
user: 'Korisnik',
username: 'Korisničko ime',
users: 'Korisnici',
value: 'Vrednost',
welcome: 'Dobrodošli',
@@ -361,8 +359,6 @@ export const rsLatinTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'Ovo polje zahteva dva broja.',
shorterThanMax: 'Ova vrednost mora biti kraća od maksimalne dužine od {{maxLength}} karaktera',
trueOrFalse: 'Ovo polje može biti samo tačno ili netačno',
username:
'Molimo unesite važeće korisničko ime. Može sadržavati slova, brojeve, crtice, tačke i donje crte.',
validUploadID: 'Ovo polje ne sadrži validan ID prenosa.',
},
version: {

View File

@@ -18,7 +18,6 @@ export const ruTranslations: DefaultTranslationsObject = {
confirmPassword: 'Подтверждение пароля',
createFirstUser: 'Создание первого пользователя',
emailNotValid: 'Указанный адрес электронной почты неверен',
emailOrUsername: 'Электронная почта или Имя пользователя',
emailSent: 'Email отправлен',
emailVerified: 'Электронная почта успешно подтверждена.',
enableAPIKey: 'Активировать API ключ',
@@ -300,7 +299,6 @@ export const ruTranslations: DefaultTranslationsObject = {
updating: 'Обновление',
uploading: 'Загрузка',
user: 'пользователь',
username: 'Имя пользователя',
users: 'пользователи',
value: 'Значение',
welcome: 'Добро пожаловать',
@@ -365,8 +363,6 @@ export const ruTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'В этом поле требуется два числа.',
shorterThanMax: 'Это значение должно быть короче максимальной длины символов {{maxLength}}.',
trueOrFalse: 'Это поле может быть равно только true или false.',
username:
'Пожалуйста, введите действительное имя пользователя. Может содержать буквы, цифры, дефисы, точки и подчёркивания.',
validUploadID: "'Это поле не является действительным ID загрузки.'",
},
version: {

View File

@@ -18,7 +18,6 @@ export const skTranslations: DefaultTranslationsObject = {
confirmPassword: 'Potvrdiť heslo',
createFirstUser: 'Vytvorenie prvého používateľa',
emailNotValid: 'Zadaný e-mail nie je platný',
emailOrUsername: 'E-mail alebo Užívateľské meno',
emailSent: 'E-mail bol odoslaný',
emailVerified: 'Email úspešne overený.',
enableAPIKey: 'Povolenie API kľúča',
@@ -298,7 +297,6 @@ export const skTranslations: DefaultTranslationsObject = {
updating: 'Aktualizácia',
uploading: 'Nahrávanie',
user: 'Používateľ',
username: 'Používateľské meno',
users: 'Používatelia',
value: 'Hodnota',
welcome: 'Vitajte',
@@ -363,8 +361,6 @@ export const skTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'Toto pole vyžaduje dve čísla.',
shorterThanMax: 'Táto hodnota musí byť kratšia ako maximálna dĺžka {{maxLength}} znakov.',
trueOrFalse: 'Toto pole môže byť rovné iba true alebo false.',
username:
'Prosím, zadajte platné používateľské meno. Môže obsahovať písmená, čísla, pomlčky, bodky a podčiarknutia.',
validUploadID: 'Toto pole nie je platné ID pre odoslanie.',
},
version: {

View File

@@ -18,7 +18,6 @@ export const svTranslations: DefaultTranslationsObject = {
confirmPassword: 'Bekräfta Lösenord',
createFirstUser: 'Skapa första användaren',
emailNotValid: 'Angiven e-postadress är inte giltig',
emailOrUsername: 'E-post eller användarnamn',
emailSent: 'E-posten Skickad',
emailVerified: 'E-post verifierad framgångsrikt.',
enableAPIKey: 'Aktivera API nyckel',
@@ -297,7 +296,6 @@ export const svTranslations: DefaultTranslationsObject = {
updating: 'Uppdatering',
uploading: 'Uppladdning',
user: 'Användare',
username: 'Användarnamn',
users: 'Användare',
value: 'Värde',
welcome: 'Välkommen',
@@ -362,8 +360,6 @@ export const svTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'Detta fält kräver två nummer.',
shorterThanMax: 'Detta värde måste vara kortare än maxlängden på {{maxLength}} tecken.',
trueOrFalse: 'Detta fält kan bara vara lika med sant eller falskt.',
username:
'Var god ange ett giltigt användarnamn. Kan innehålla bokstäver, siffror, bindestreck, punkter och understreck.',
validUploadID: 'Det här fältet är inte ett giltigt uppladdnings-ID',
},
version: {

View File

@@ -18,7 +18,6 @@ export const thTranslations: DefaultTranslationsObject = {
confirmPassword: 'ยืนยันรหัสผ่าน',
createFirstUser: 'สร้างผู้ใช้แรก',
emailNotValid: 'อีเมลไม่ถูกต้อง',
emailOrUsername: 'อีเมลหรือชื่อผู้ใช้',
emailSent: 'ส่งอีเมลเรียบร้อยแล้ว',
emailVerified: 'อีเมลได้รับการยืนยันเรียบร้อยแล้ว',
enableAPIKey: 'เปิดใช้ API Key',
@@ -293,7 +292,6 @@ export const thTranslations: DefaultTranslationsObject = {
updating: 'กำลังอัปเดต',
uploading: 'กำลังอัปโหลด',
user: 'ผู้ใช้',
username: 'ชื่อผู้ใช้',
users: 'ผู้ใช้',
value: 'ค่า',
welcome: 'ยินดีต้อนรับ',
@@ -356,7 +354,6 @@ export const thTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'ต้องมีตัวเลข 2 ค่า',
shorterThanMax: 'ค่าต้องมีความยาวน้อยกว่า {{maxLength}} ตัวอักษร',
trueOrFalse: 'เป็นได้แค่ "ใช่" หรือ "ไม่ใช่"',
username: 'กรุณาใส่ชื่อผู้ใช้ที่ถูกต้อง สามารถมีตัวอักษร ตัวเลข ขีดกลาง จุด และขีดล่าง',
validUploadID: 'ไม่ใช่ ID ของการอัปโหลดที่ถูกต้อง',
},
version: {

View File

@@ -18,7 +18,6 @@ export const trTranslations: DefaultTranslationsObject = {
confirmPassword: 'Parolayı Onayla',
createFirstUser: 'İlk kullanıcı oluştur',
emailNotValid: 'Girilen e-posta geçersiz',
emailOrUsername: 'E-posta veya Kullanıcı Adı',
emailSent: 'E-posta gönderildi',
emailVerified: 'E-posta başarıyla doğrulandı.',
enableAPIKey: 'Api anahtarını etkinleştir',
@@ -301,7 +300,6 @@ export const trTranslations: DefaultTranslationsObject = {
updating: 'Güncelleniyor',
uploading: 'Yükleniyor',
user: 'kullanıcı',
username: 'Kullanıcı Adı',
users: 'kullanıcı',
value: 'Değer',
welcome: 'Hoşgeldiniz',
@@ -366,8 +364,6 @@ export const trTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'Bu alana en az iki rakam girilmesi zorunludur.',
shorterThanMax: 'Bu alan {{maxLength}} karakterden daha kısa olmalıdır.',
trueOrFalse: 'Bu alan yalnızca doğru ve yanlış olabilir.',
username:
'Lütfen geçerli bir kullanıcı adı girin. Harfler, numaralar, kısa çizgiler, noktalar ve alt çizgiler içerebilir.',
validUploadID: "'Bu alan geçerli bir karşıya yükleme ID'sine sahip değil.'",
},
version: {

View File

@@ -18,7 +18,6 @@ export const ukTranslations: DefaultTranslationsObject = {
confirmPassword: 'Підтвердження паролю',
createFirstUser: 'Створення першого користувача',
emailNotValid: 'Вказана адреса електронної пошти недійсна',
emailOrUsername: "Електронна пошта або Ім'я користувача",
emailSent: 'Лист відправлено',
emailVerified: 'Електронну пошту успішно підтверджено.',
enableAPIKey: 'Активувати API ключ',
@@ -297,7 +296,6 @@ export const ukTranslations: DefaultTranslationsObject = {
updating: 'оновлення',
uploading: 'завантаження',
user: 'Користувач',
username: "Ім'я користувача",
users: 'Користувачі',
value: 'Значення',
welcome: 'Вітаю',
@@ -362,8 +360,6 @@ export const ukTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'У цьому полі потрібно ввести два числа.',
shorterThanMax: 'Це значення має дорівнювати або бути коротшим, ніж {{maxLength}} символів.',
trueOrFalse: 'Це поле може мати значення тільки true або false.',
username:
"Будь ласка, введіть дійсне ім'я користувача. Може містити літери, цифри, дефіси, крапки та підкреслення.",
validUploadID: 'Це поле не є дійсним ID завантаження.',
},
version: {

View File

@@ -17,7 +17,6 @@ export const viTranslations: DefaultTranslationsObject = {
confirmPassword: 'Xác nhận mật khẩu',
createFirstUser: 'Tạo người dùng đầu tiên',
emailNotValid: 'Email không chính xác',
emailOrUsername: 'Email hoặc Tên tài khoản',
emailSent: 'Email đã được gửi',
emailVerified: 'Email đã được xác minh thành công.',
enableAPIKey: 'Kích hoạt API Key',
@@ -295,7 +294,6 @@ export const viTranslations: DefaultTranslationsObject = {
updating: 'Đang cập nhật',
uploading: 'Đang tải lên',
user: 'Người dùng',
username: 'Tên đăng nhập',
users: 'Người dùng',
value: 'Giá trị',
welcome: 'Xin chào',
@@ -360,8 +358,6 @@ export const viTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: 'Field này cần tối thiểu 2 chữ số.',
shorterThanMax: 'Giá trị phải ngắn hơn hoặc bằng {{maxLength}} ký tự.',
trueOrFalse: 'Field này chỉ có thể chứa giá trị true hoặc false.',
username:
'Vui lòng nhập một tên người dùng hợp lệ. Có thể chứa các chữ cái, số, dấu gạch ngang, dấu chấm và dấu gạch dưới.',
validUploadID: "'Field này không chứa ID tải lên hợp lệ.'",
},
version: {

View File

@@ -17,7 +17,6 @@ export const zhTranslations: DefaultTranslationsObject = {
confirmPassword: '确认密码',
createFirstUser: '创建第一个用户',
emailNotValid: '所提供的电子邮件时无效的',
emailOrUsername: '电子邮件或用户名',
emailSent: '电子邮件已发送',
emailVerified: '电子邮件验证成功。',
enableAPIKey: '启用API密钥',
@@ -289,7 +288,6 @@ export const zhTranslations: DefaultTranslationsObject = {
updating: '更新中',
uploading: '上传中',
user: '用户',
username: '用户名',
users: '用户',
value: '值',
welcome: '欢迎',
@@ -352,7 +350,6 @@ export const zhTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: '该字段需要两个数字。',
shorterThanMax: '该值必须小于{{maxLength}}字符的最大长度',
trueOrFalse: '该字段只能等于真或伪。',
username: '请输入一个有效的用户名。可包含字母,数字,连字符,句点和下划线。',
validUploadID: '该字段不是有效的上传ID。',
},
version: {

View File

@@ -17,7 +17,6 @@ export const zhTwTranslations: DefaultTranslationsObject = {
confirmPassword: '確認密碼',
createFirstUser: '建立第一個使用者',
emailNotValid: '提供的電子郵件無效',
emailOrUsername: '電子郵件或使用者名稱',
emailSent: '電子郵件已寄出',
emailVerified: '電子郵件驗證成功。',
enableAPIKey: '啟用API金鑰',
@@ -289,7 +288,6 @@ export const zhTwTranslations: DefaultTranslationsObject = {
updating: '更新中',
uploading: '上傳中',
user: '使用者',
username: '使用者名稱',
users: '使用者',
value: '值',
welcome: '歡迎',
@@ -352,7 +350,6 @@ export const zhTwTranslations: DefaultTranslationsObject = {
requiresTwoNumbers: '該字串需要兩個數字。',
shorterThanMax: '該值長度必須小於{{maxLength}}個字元',
trueOrFalse: '該字串只能等於是或否。',
username: '請輸入有效的使用者名稱。可以包含字母、數字、連字號、句點和底線。',
validUploadID: '該字串不是有效的上傳ID。',
},
version: {

View File

@@ -1,6 +1,6 @@
{
"name": "@payloadcms/ui",
"version": "3.0.0-beta.66",
"version": "3.0.0-beta.65",
"homepage": "https://payloadcms.com",
"repository": {
"type": "git",

View File

@@ -24,8 +24,9 @@ export const MultiValueLabel: React.FC<MultiValueProps<Option>> = (props) => {
// @ts-expect-error-next-line// TODO Fix this - moduleResolution 16 breaks our declare module
draggableProps,
// @ts-expect-error-next-line // TODO Fix this - moduleResolution 16 breaks our declare module
onSave,
// @ts-expect-error-next-line // TODO Fix this - moduleResolution 16 breaks our declare module
setDrawerIsOpen,
// onSave,
} = {},
} = {},
} = props
@@ -77,7 +78,7 @@ export const MultiValueLabel: React.FC<MultiValueProps<Option>> = (props) => {
</Tooltip>
<EditIcon className={`${baseClass}__icon`} />
</DocumentDrawerToggler>
<DocumentDrawer onSave={/* onSave */ null} />
<DocumentDrawer onSave={onSave} />
</Fragment>
)}
</div>

1820
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,13 @@
import type { CollectionConfig } from 'payload'
import {
BlocksFeature,
FixedToolbarFeature,
HeadingFeature,
HorizontalRuleFeature,
InlineToolbarFeature,
lexicalEditor } from '@payloadcms/richtext-lexical'
lexicalEditor,
} from '@payloadcms/richtext-lexical'
import { BlocksFeature } from '@payloadcms/richtext-lexical'
import { authenticated } from '../../access/authenticated'
import { authenticatedOrPublished } from '../../access/authenticatedOrPublished'

View File

@@ -460,42 +460,11 @@ describe('Auth', () => {
await tryLogin()
await tryLogin()
const loginAfterLimit = await restClient
.POST(`/${slug}/login`, {
body: JSON.stringify({
email: userEmail,
password,
}),
headers: {
Authorization: `JWT ${token}`,
'Content-Type': 'application/json',
},
method: 'post',
})
.then((res) => res.json())
expect(loginAfterLimit.errors.length).toBeGreaterThan(0)
const lockedUser = await payload.find({
collection: slug,
showHiddenFields: true,
where: {
email: {
equals: userEmail,
},
},
})
expect(lockedUser.docs[0].loginAttempts).toBe(2)
expect(lockedUser.docs[0].lockUntil).toBeDefined()
const manuallyReleaseLock = new Date(Date.now() - 605 * 1000)
const userLockElapsed = await payload.update({
await payload.update({
collection: slug,
data: {
lockUntil: manuallyReleaseLock,
lockUntil: Date.now() - 605 * 1000,
},
showHiddenFields: true,
where: {
email: {
equals: userEmail,
@@ -503,8 +472,6 @@ describe('Auth', () => {
},
})
expect(userLockElapsed.docs[0].lockUntil).toEqual(manuallyReleaseLock.toISOString())
// login
await restClient.POST(`/${slug}/login`, {
body: JSON.stringify({

View File

@@ -207,6 +207,6 @@ export interface Auth {
declare module 'payload' {
// @ts-ignore
// @ts-ignore
export interface GeneratedTypes extends Config {}
}
}

View File

@@ -98,21 +98,21 @@ export class NextRESTClient {
const url = `${this.serverURL}${this.config.routes.api}/${slugs}`
return {
url,
slug: slugs.split('/'),
params: params ? qs.parse(params) : undefined,
url,
}
}
async DELETE(path: ValidPath, options: RequestInit & RequestOptions = {}): Promise<Response> {
const { slug, params, url } = this.generateRequestParts(path)
const { url, slug, params } = this.generateRequestParts(path)
const { query, ...rest } = options || {}
const queryParams = generateQueryString(query, params)
const request = new Request(`${url}${queryParams}`, {
...rest,
headers: this.buildHeaders(options),
method: 'DELETE',
headers: this.buildHeaders(options),
})
return this._DELETE(request, { params: { slug } })
}
@@ -121,14 +121,14 @@ export class NextRESTClient {
path: ValidPath,
options: Omit<RequestInit, 'body'> & RequestOptions = {},
): Promise<Response> {
const { slug, params, url } = this.generateRequestParts(path)
const { url, slug, params } = this.generateRequestParts(path)
const { query, ...rest } = options || {}
const queryParams = generateQueryString(query, params)
const request = new Request(`${url}${queryParams}`, {
...rest,
headers: this.buildHeaders(options),
method: 'GET',
headers: this.buildHeaders(options),
})
return this._GET(request, { params: { slug } })
}
@@ -140,8 +140,8 @@ export class NextRESTClient {
`${this.serverURL}${this.config.routes.api}${this.config.routes.graphQL}${queryParams}`,
{
...rest,
headers: this.buildHeaders(options),
method: 'POST',
headers: this.buildHeaders(options),
},
)
return this._GRAPHQL_POST(request)
@@ -154,8 +154,8 @@ export class NextRESTClient {
const request = new Request(`${url}${queryParams}`, {
...rest,
headers: this.buildHeaders(options),
method: 'PATCH',
headers: this.buildHeaders(options),
})
return this._PATCH(request, { params: { slug } })
}
@@ -164,12 +164,12 @@ export class NextRESTClient {
path: ValidPath,
options: FileArg & RequestInit & RequestOptions = {},
): Promise<Response> {
const { slug, params, url } = this.generateRequestParts(path)
const { url, slug, params } = this.generateRequestParts(path)
const queryParams = generateQueryString({}, params)
const request = new Request(`${url}${queryParams}`, {
...options,
headers: this.buildHeaders(options),
method: 'POST',
headers: this.buildHeaders(options),
})
return this._POST(request, { params: { slug } })
}