- added backup codes for 2 factor authentication
Some checks failed
CI Pipeline / japa-tests (push) Failing after 58s
Some checks failed
CI Pipeline / japa-tests (push) Failing after 58s
- npm updates - coverage validation: elevation ust be positive, depth must be negative - vinejs-provider.js: get enabled extensions from database, not via validOptions.extnames - vue components for backup codes: e.g.: PersonalSettings.vue - validate spaital coverage in leaflet map: draw.component.vue, map.component.vue - add backup code authentication into Login.vue - preset to use no preferred reviewer: Release.vue - 2 new vinejs validation rules: file_scan.ts and file-length.ts
This commit is contained in:
parent
ac473b1e72
commit
005df2e454
32 changed files with 1416 additions and 526 deletions
|
@ -1,11 +1,11 @@
|
|||
import type { HttpContext } from '@adonisjs/core/http';
|
||||
// import TotpSecret from 'App/Models/TotpSecret';
|
||||
import User from '#models/user';
|
||||
import TwoFactorAuthProvider from '#app/services/TwoFactorAuthProvider';
|
||||
import { StatusCodes } from 'http-status-codes';
|
||||
import { InvalidArgumentException } from 'node-exceptions';
|
||||
import { TotpState } from '#contracts/enums';
|
||||
|
||||
import BackupCodeStorage, { SecureRandom } from '#services/backup_code_storage';
|
||||
import BackupCode from '#models/backup_code';
|
||||
|
||||
// Here we are generating secret and recovery codes for the user that’s enabling 2FA and storing them to our database.
|
||||
export default class UserController {
|
||||
|
@ -28,15 +28,20 @@ export default class UserController {
|
|||
case TotpState.STATE_DISABLED:
|
||||
// user.twoFactorSecret = null;
|
||||
// user.twoFactorRecoveryCodes = null;
|
||||
user.twoFactorSecret = "";
|
||||
user.twoFactorRecoveryCodes = [""];
|
||||
await BackupCode.deleteCodes(user);
|
||||
user.twoFactorSecret = '';
|
||||
// user.twoFactorRecoveryCodes = [''];
|
||||
await user.save();
|
||||
|
||||
|
||||
user.state = TotpState.STATE_DISABLED;
|
||||
await user.save();
|
||||
|
||||
let storage = new BackupCodeStorage(new SecureRandom());
|
||||
let backupState = await storage.getBackupCodesState(user);
|
||||
|
||||
return response.status(StatusCodes.OK).json({
|
||||
state: TotpState.STATE_DISABLED,
|
||||
backupState: backupState,
|
||||
});
|
||||
case TotpState.STATE_CREATED:
|
||||
user.twoFactorSecret = TwoFactorAuthProvider.generateSecret(user);
|
||||
|
@ -56,8 +61,8 @@ export default class UserController {
|
|||
if (!code) {
|
||||
throw new InvalidArgumentException('code is missing');
|
||||
}
|
||||
const success = await TwoFactorAuthProvider.enable(user, code)
|
||||
|
||||
const success = await TwoFactorAuthProvider.enable(user, code);
|
||||
|
||||
return response.status(StatusCodes.OK).json({
|
||||
state: success ? TotpState.STATE_ENABLED : TotpState.STATE_CREATED,
|
||||
});
|
||||
|
@ -79,4 +84,31 @@ export default class UserController {
|
|||
// recoveryCodes: user.twoFactorRecoveryCodes,
|
||||
// });
|
||||
// }
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @PasswordConfirmationRequired
|
||||
*
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public async createCodes({ auth, response }: HttpContext) {
|
||||
// $user = $this->userSession->getUser();
|
||||
const user = (await User.find(auth.user?.id)) as User;
|
||||
|
||||
// let codes = TwoFactorAuthProvider.generateRecoveryCodes();
|
||||
let storage = new BackupCodeStorage(new SecureRandom());
|
||||
// $codes = $this->storage->createCodes($user);
|
||||
const codes = await storage.createCodes(user);
|
||||
|
||||
let backupState = await storage.getBackupCodesState(user);
|
||||
// return new JSONResponse([
|
||||
// 'codes' => $codes,
|
||||
// 'state' => $this->storage->getBackupCodesState($user),
|
||||
// ]);
|
||||
return response.status(StatusCodes.OK).json({
|
||||
codes: codes,
|
||||
// state: success ? TotpState.STATE_ENABLED : TotpState.STATE_CREATED,
|
||||
backupState: backupState, //storage.getBackupCodesState(user),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import type { HttpContext } from '@adonisjs/core/http';
|
||||
import User from '#models/user';
|
||||
import BackupCode from '#models/backup_code';
|
||||
// import Hash from '@ioc:Adonis/Core/Hash';
|
||||
// import InvalidCredentialException from 'App/Exceptions/InvalidCredentialException';
|
||||
import { authValidator } from '#validators/auth';
|
||||
import hash from '@adonisjs/core/services/hash';
|
||||
|
||||
import TwoFactorAuthProvider from '#app/services/TwoFactorAuthProvider';
|
||||
// import { Authenticator } from '@adonisjs/auth';
|
||||
|
@ -31,22 +33,22 @@ export default class AuthController {
|
|||
// await auth.use('web').attempt(email, plainPassword);
|
||||
|
||||
// const user = await auth.use('web').verifyCredentials(email, password);
|
||||
const user = await User.verifyCredentials(email, password)
|
||||
|
||||
const user = await User.verifyCredentials(email, password);
|
||||
|
||||
if (user.isTwoFactorEnabled) {
|
||||
// session.put("login.id", user.id);
|
||||
// return view.render("pages/two-factor-challenge");
|
||||
|
||||
session.flash('user_id', user.id);
|
||||
return response.redirect().back();
|
||||
|
||||
|
||||
// let state = LoginState.STATE_VALIDATED;
|
||||
// return response.status(StatusCodes.OK).json({
|
||||
// state: state,
|
||||
// new_user_id: user.id,
|
||||
// });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
await auth.use('web').login(user);
|
||||
} catch (error) {
|
||||
// if login fails, return vague form message and redirect back
|
||||
|
@ -59,10 +61,9 @@ export default class AuthController {
|
|||
}
|
||||
|
||||
public async twoFactorChallenge({ request, session, auth, response }: HttpContext) {
|
||||
const { code, recoveryCode, login_id } = request.only(['code', 'recoveryCode', 'login_id']);
|
||||
// const user = await User.query().where('id', session.get('login.id')).firstOrFail();
|
||||
const { code, backup_code, login_id } = request.only(['code', 'backup_code', 'login_id']);
|
||||
const user = await User.query().where('id', login_id).firstOrFail();
|
||||
|
||||
|
||||
if (code) {
|
||||
const isValid = await TwoFactorAuthProvider.validate(user, code);
|
||||
if (isValid) {
|
||||
|
@ -70,17 +71,46 @@ export default class AuthController {
|
|||
await auth.use('web').login(user);
|
||||
response.redirect('/apps/dashboard');
|
||||
} else {
|
||||
session.flash('message', 'Your tow factor code is incorrect');
|
||||
session.flash('message', 'Your two-factor code is incorrect');
|
||||
return response.redirect().back();
|
||||
}
|
||||
} else if (backup_code) {
|
||||
const codes: BackupCode[] = await user.getBackupCodes();
|
||||
|
||||
// const verifiedBackupCodes = await Promise.all(
|
||||
// codes.map(async (backupCode) => {
|
||||
// let isVerified = await hash.verify(backupCode.code, backup_code);
|
||||
// if (isVerified) {
|
||||
// return backupCode;
|
||||
// }
|
||||
// }),
|
||||
// );
|
||||
// const backupCodeToDelete = verifiedBackupCodes.find(Boolean);
|
||||
|
||||
let backupCodeToDelete = null;
|
||||
for (const backupCode of codes) {
|
||||
const isVerified = await hash.verify(backupCode.code, backup_code);
|
||||
if (isVerified) {
|
||||
backupCodeToDelete = backupCode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (backupCodeToDelete) {
|
||||
if (backupCodeToDelete.used === false) {
|
||||
backupCodeToDelete.used = true;
|
||||
await backupCodeToDelete.save();
|
||||
console.log(`BackupCode with id ${backupCodeToDelete.id} has been marked as used.`);
|
||||
await auth.use('web').login(user);
|
||||
response.redirect('/apps/dashboard');
|
||||
} else {
|
||||
session.flash('message', 'BackupCode already used');
|
||||
return response.redirect().back();
|
||||
}
|
||||
} else {
|
||||
session.flash('message', 'BackupCode not found');
|
||||
return response.redirect().back();
|
||||
}
|
||||
} else if (recoveryCode) {
|
||||
const codes = user?.twoFactorRecoveryCodes ?? [];
|
||||
if (codes.includes(recoveryCode)) {
|
||||
user.twoFactorRecoveryCodes = codes.filter((c) => c !== recoveryCode);
|
||||
await user.save();
|
||||
await auth.use('web').login(user);
|
||||
response.redirect('/apps/dashboard');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import TwoFactorAuthProvider from '#app/services/TwoFactorAuthProvider';
|
|||
import hash from '@adonisjs/core/services/hash';
|
||||
// import { schema, rules } from '@adonisjs/validator';
|
||||
import vine from '@vinejs/vine';
|
||||
import BackupCodeStorage, { SecureRandom } from '#services/backup_code_storage';
|
||||
|
||||
// Here we are generating secret and recovery codes for the user that’s enabling 2FA and storing them to our database.
|
||||
export default class UserController {
|
||||
|
@ -19,10 +20,15 @@ export default class UserController {
|
|||
// const id = request.param('id');
|
||||
// const user = await User.query().where('id', id).firstOrFail();
|
||||
|
||||
let storage = new BackupCodeStorage(new SecureRandom());
|
||||
// const codes= user.isTwoFactorEnabled? (await user.getBackupCodes()).map((role) => role.code) : [];
|
||||
let backupState = await storage.getBackupCodesState(user);
|
||||
|
||||
return inertia.render('Auth/AccountInfo', {
|
||||
user: user,
|
||||
twoFactorEnabled: user.isTwoFactorEnabled,
|
||||
// code: await TwoFactorAuthProvider.generateQrCode(user),
|
||||
backupState: backupState,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -327,13 +327,13 @@ export default class DatasetController {
|
|||
x_max: vine.number(),
|
||||
y_min: vine.number(),
|
||||
y_max: vine.number(),
|
||||
elevation_absolut: vine.number().optional(),
|
||||
elevation_min: vine.number().optional().requiredIfExists('elevation_max'),
|
||||
elevation_max: vine.number().optional().requiredIfExists('elevation_min'),
|
||||
elevation_absolut: vine.number().positive().optional(),
|
||||
elevation_min: vine.number().positive().optional().requiredIfExists('elevation_max'),
|
||||
elevation_max: vine.number().positive().optional().requiredIfExists('elevation_min'),
|
||||
// type: vine.enum(Object.values(DescriptionTypes)),
|
||||
depth_absolut: vine.number().optional(),
|
||||
depth_min: vine.number().optional().requiredIfExists('depth_max'),
|
||||
depth_max: vine.number().optional().requiredIfExists('depth_min'),
|
||||
depth_absolut: vine.number().negative().optional(),
|
||||
depth_min: vine.number().negative().optional().requiredIfExists('depth_max'),
|
||||
depth_max: vine.number().negative().optional().requiredIfExists('depth_min'),
|
||||
}),
|
||||
references: vine
|
||||
.array(
|
||||
|
|
51
app/models/backup_code.ts
Normal file
51
app/models/backup_code.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import BaseModel from './base_model.js';
|
||||
import { column, SnakeCaseNamingStrategy, belongsTo } from '@adonisjs/lucid/orm';
|
||||
import User from './user.js';
|
||||
import type { BelongsTo } from '@adonisjs/lucid/types/relations';
|
||||
import db from '@adonisjs/lucid/services/db';
|
||||
import hash from '@adonisjs/core/services/hash';
|
||||
|
||||
export default class BackupCode extends BaseModel {
|
||||
public static table = 'backupcodes';
|
||||
public static namingStrategy = new SnakeCaseNamingStrategy();
|
||||
|
||||
@column({
|
||||
isPrimary: true,
|
||||
})
|
||||
public id: number;
|
||||
|
||||
@column({})
|
||||
public user_id: number;
|
||||
|
||||
@column({
|
||||
// serializeAs: null,
|
||||
// consume: (value: string) => (value ? JSON.parse(encryption.decrypt(value) ?? '{}') : null),
|
||||
// prepare: (value: string) => encryption.encrypt(JSON.stringify(value)),
|
||||
})
|
||||
public code: string;
|
||||
|
||||
@column({})
|
||||
public used: boolean;
|
||||
|
||||
@belongsTo(() => User, {
|
||||
foreignKey: 'user_id',
|
||||
})
|
||||
public user: BelongsTo<typeof User>;
|
||||
|
||||
// public static async getBackupCodes(user: User): Promise<BackupCode[]> {
|
||||
// return await db.from(this.table).select('id', 'user_id', 'code', 'used').where('user_id', user.id);
|
||||
// }
|
||||
|
||||
public static async deleteCodes(user: User): Promise<void> {
|
||||
await db.from(this.table).where('user_id', user.id).delete();
|
||||
}
|
||||
|
||||
public static async deleteCodesByUserId(uid: string): Promise<void> {
|
||||
await db.from(this.table).where('user_id', uid).delete();
|
||||
}
|
||||
|
||||
// Method to verify password
|
||||
public async verifyCode(plainCode: string) {
|
||||
return await hash.verify(this.code, plainCode);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { DateTime } from 'luxon';
|
||||
import { withAuthFinder } from '@adonisjs/auth/mixins/lucid'
|
||||
import { withAuthFinder } from '@adonisjs/auth/mixins/lucid';
|
||||
import { column, manyToMany, hasMany } from '@adonisjs/lucid/orm';
|
||||
import hash from '@adonisjs/core/services/hash';
|
||||
import Role from './role.js';
|
||||
|
@ -13,6 +13,7 @@ import { TotpState } from '#contracts/enums';
|
|||
import type { ManyToMany } from '@adonisjs/lucid/types/relations';
|
||||
import type { HasMany } from '@adonisjs/lucid/types/relations';
|
||||
import { compose } from '@adonisjs/core/helpers';
|
||||
import BackupCode from './backup_code.js';
|
||||
|
||||
const AuthFinder = withAuthFinder(() => hash.use('laravel'), {
|
||||
uids: ['email'],
|
||||
|
@ -107,6 +108,19 @@ export default class User extends compose(BaseModel, AuthFinder) {
|
|||
})
|
||||
public datasets: HasMany<typeof Dataset>;
|
||||
|
||||
@hasMany(() => BackupCode, {
|
||||
foreignKey: 'user_id',
|
||||
})
|
||||
public backupcodes: HasMany<typeof BackupCode>;
|
||||
|
||||
|
||||
|
||||
public async getBackupCodes(this: User): Promise<BackupCode[]> {
|
||||
const test = await this.related('backupcodes').query();
|
||||
// return test.map((role) => role.code);
|
||||
return test;
|
||||
}
|
||||
|
||||
// https://github.com/adonisjs/core/discussions/1872#discussioncomment-132289
|
||||
public async getRoles(this: User): Promise<string[]> {
|
||||
const test = await this.related('roles').query();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// import Config from '@ioc:Adonis/Core/Config';
|
||||
import config from '@adonisjs/core/services/config'
|
||||
// import config from '@adonisjs/core/services/config'
|
||||
import env from '#start/env';
|
||||
import User from '#models/user';
|
||||
import { generateSecret, verifyToken } from 'node-2fa/dist/index.js';
|
||||
// import cryptoRandomString from 'crypto-random-string';
|
||||
|
@ -14,7 +15,7 @@ import { TotpState } from '#contracts/enums';
|
|||
// npm i --save-dev @types/qrcode
|
||||
|
||||
class TwoFactorAuthProvider {
|
||||
private issuer: string = config.get('twoFactorAuthConfig.app.name') || 'TethysCloud';
|
||||
private issuer: string = env.get('APP_NAME') || 'TethysCloud';
|
||||
|
||||
/**
|
||||
* generateSecret will generate a user-specific 32-character secret.
|
||||
|
@ -41,7 +42,7 @@ class TwoFactorAuthProvider {
|
|||
* Return recovery codes
|
||||
* @return {string[]}
|
||||
*/
|
||||
public generateRecoveryCodes() {
|
||||
public generateRecoveryCodes(): string[] {
|
||||
const recoveryCodeLimit: number = 8;
|
||||
const codes: string[] = [];
|
||||
for (let i = 0; i < recoveryCodeLimit; i++) {
|
||||
|
|
136
app/services/backup_code_storage.ts
Normal file
136
app/services/backup_code_storage.ts
Normal file
|
@ -0,0 +1,136 @@
|
|||
import User from '#models/user';
|
||||
import BackupCode from '#models/backup_code';
|
||||
import hash from '@adonisjs/core/services/hash';
|
||||
|
||||
export interface ISecureRandom {
|
||||
CHAR_UPPER: string;
|
||||
CHAR_LOWER: string;
|
||||
CHAR_DIGITS: string;
|
||||
CHAR_SYMBOLS: string;
|
||||
CHAR_ALPHANUMERIC: string;
|
||||
CHAR_HUMAN_READABLE: string;
|
||||
|
||||
/**
|
||||
* Generate a random string of specified length.
|
||||
* @param int $length The length of the generated string
|
||||
* @param string $characters An optional list of characters to use if no character list is
|
||||
* specified all valid base64 characters are used.
|
||||
* @return string
|
||||
* @since 8.0.0
|
||||
*/
|
||||
generate(length: number, characters?: string): string;
|
||||
}
|
||||
|
||||
export class SecureRandom implements ISecureRandom {
|
||||
CHAR_UPPER: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
CHAR_LOWER: string = 'abcdefghijklmnopqrstuvwxyz';
|
||||
CHAR_DIGITS: string = '0123456789';
|
||||
CHAR_SYMBOLS: string = '!"#$%&\\\'()*+,-./:;<=>?@[]^_`{|}~';
|
||||
CHAR_ALPHANUMERIC: string = this.CHAR_UPPER + this.CHAR_LOWER + this.CHAR_DIGITS;
|
||||
CHAR_HUMAN_READABLE: string = 'abcdefgijkmnopqrstwxyzABCDEFGHJKLMNPQRSTWXYZ23456789';
|
||||
|
||||
public generate(length: number, characters: string = this.CHAR_ALPHANUMERIC): string {
|
||||
if (length <= 0) {
|
||||
throw new Error('Invalid length specified: ' + length + ' must be bigger than 0');
|
||||
}
|
||||
const maxCharIndex: number = characters.length - 1;
|
||||
let randomString: string = '';
|
||||
while (length > 0) {
|
||||
const randomNumber: number = Math.floor(Math.random() * (maxCharIndex + 1));
|
||||
randomString += characters[randomNumber];
|
||||
length--;
|
||||
}
|
||||
return randomString;
|
||||
}
|
||||
}
|
||||
|
||||
class BackupCodeStorage {
|
||||
private static CODE_LENGTH: number = 16;
|
||||
// private mapper: BackupCodeMapper;
|
||||
// private hasher: IHasher;
|
||||
private random: ISecureRandom;
|
||||
// private eventDispatcher: IEventDispatcher;
|
||||
|
||||
// constructor(mapper: BackupCodeMapper, random: ISecureRandom, hasher: IHasher, eventDispatcher: IEventDispatcher) {
|
||||
// this.mapper = mapper;
|
||||
// this.hasher = hasher;
|
||||
// this.random = random;
|
||||
// this.eventDispatcher = eventDispatcher;
|
||||
// }
|
||||
constructor(random: ISecureRandom) {
|
||||
// this.mapper = mapper;
|
||||
// this.hasher = hasher;
|
||||
this.random = random;
|
||||
// this.eventDispatcher = eventDispatcher;
|
||||
}
|
||||
|
||||
public async createCodes(user: User, number: number = 10): Promise<string[]> {
|
||||
let results: string[] = [];
|
||||
// this.mapper.deleteCodes(user);
|
||||
await BackupCode.deleteCodes(user);
|
||||
// user.twoFactorRecoveryCodes = [""];
|
||||
|
||||
// const uid = user.getUID();
|
||||
for (let i = 1; i <= Math.min(number, 20); i++) {
|
||||
const code = this.random.generate(BackupCodeStorage.CODE_LENGTH, this.random.CHAR_HUMAN_READABLE);
|
||||
// const code = crypto
|
||||
// .randomBytes(Math.ceil(BackupCodeStorage.CODE_LENGTH / 2))
|
||||
// .toString('hex')
|
||||
// .slice(0, BackupCodeStorage.CODE_LENGTH);
|
||||
const dbCode = new BackupCode();
|
||||
// dbCode.setUserId(uid);
|
||||
|
||||
// dbCode.setCode(this.hasher.hash(code));
|
||||
dbCode.code = await hash.make(code);
|
||||
// dbCode.setUsed(0);
|
||||
dbCode.used = false;
|
||||
// this.mapper.insert(dbCode);
|
||||
// await dbCode.save();
|
||||
await dbCode.related('user').associate(user); // speichert schon ab
|
||||
results.push(code);
|
||||
}
|
||||
// this.eventDispatcher.dispatchTyped(new CodesGenerated(user));
|
||||
return results;
|
||||
}
|
||||
|
||||
public async hasBackupCodes(user: User): Promise<boolean> {
|
||||
const codes = await user.getBackupCodes();
|
||||
return codes.length > 0;
|
||||
}
|
||||
|
||||
public async getBackupCodesState(user: User) {
|
||||
// const codes = this.mapper.getBackupCodes(user);
|
||||
// const codes = await user.related('backupcodes').query().exec();
|
||||
const codes: BackupCode[] = await user.getBackupCodes();
|
||||
const total = codes.length;
|
||||
let used: number = 0;
|
||||
codes.forEach((code) => {
|
||||
if (code.used === true) {
|
||||
used++;
|
||||
}
|
||||
});
|
||||
return {
|
||||
enabled: total > 0,
|
||||
total: total,
|
||||
used: used,
|
||||
};
|
||||
}
|
||||
|
||||
// public validateCode(user: User, code: string): boolean {
|
||||
// const dbCodes = await user.getBackupCodes();
|
||||
// for (const dbCode of dbCodes) {
|
||||
// if (parseInt(dbCode.getUsed()) === 0 && this.hasher.verify(code, dbCode.getCode())) {
|
||||
// dbCode.setUsed(1);
|
||||
// this.mapper.update(dbCode);
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// public deleteCodes(user: User): void {
|
||||
// this.mapper.deleteCodes(user);
|
||||
// }
|
||||
}
|
||||
|
||||
export default BackupCodeStorage;
|
|
@ -4,9 +4,11 @@ import dayjs from 'dayjs';
|
|||
import MimeType from '#models/mime_type';
|
||||
|
||||
const enabledExtensions = await MimeType.query().select('file_extension').where('enabled', true).exec();
|
||||
const extensions = enabledExtensions.map((extension)=> {
|
||||
return extension.file_extension.split('|')
|
||||
}).flat();
|
||||
const extensions = enabledExtensions
|
||||
.map((extension) => {
|
||||
return extension.file_extension.split('|');
|
||||
})
|
||||
.flat();
|
||||
|
||||
/**
|
||||
* Validates the dataset's creation action
|
||||
|
@ -55,7 +57,13 @@ export const createDatasetValidator = vine.compile(
|
|||
authors: vine
|
||||
.array(
|
||||
vine.object({
|
||||
email: vine.string().trim().maxLength(255).email().normalizeEmail().isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
||||
email: vine
|
||||
.string()
|
||||
.trim()
|
||||
.maxLength(255)
|
||||
.email()
|
||||
.normalizeEmail()
|
||||
.isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
||||
first_name: vine.string().trim().minLength(3).maxLength(255),
|
||||
last_name: vine.string().trim().minLength(3).maxLength(255),
|
||||
}),
|
||||
|
@ -65,14 +73,20 @@ export const createDatasetValidator = vine.compile(
|
|||
contributors: vine
|
||||
.array(
|
||||
vine.object({
|
||||
email: vine.string().trim().maxLength(255).email().normalizeEmail().isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
||||
email: vine
|
||||
.string()
|
||||
.trim()
|
||||
.maxLength(255)
|
||||
.email()
|
||||
.normalizeEmail()
|
||||
.isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
||||
first_name: vine.string().trim().minLength(3).maxLength(255),
|
||||
last_name: vine.string().trim().minLength(3).maxLength(255),
|
||||
pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
|
||||
}),
|
||||
)
|
||||
.distinct('email')
|
||||
.optional(),
|
||||
.optional(),
|
||||
// third step
|
||||
project_id: vine.number().optional(),
|
||||
// embargo_date: schema.date.optional({ format: 'yyyy-MM-dd' }, [rules.after(10, 'days')]),
|
||||
|
@ -89,13 +103,13 @@ export const createDatasetValidator = vine.compile(
|
|||
x_max: vine.number(),
|
||||
y_min: vine.number(),
|
||||
y_max: vine.number(),
|
||||
elevation_absolut: vine.number().optional(),
|
||||
elevation_min: vine.number().optional().requiredIfExists('elevation_max'),
|
||||
elevation_max: vine.number().optional().requiredIfExists('elevation_min'),
|
||||
elevation_absolut: vine.number().positive().optional(),
|
||||
elevation_min: vine.number().positive().optional().requiredIfExists('elevation_max'),
|
||||
elevation_max: vine.number().positive().optional().requiredIfExists('elevation_min'),
|
||||
// type: vine.enum(Object.values(DescriptionTypes)),
|
||||
depth_absolut: vine.number().optional(),
|
||||
depth_min: vine.number().optional().requiredIfExists('depth_max'),
|
||||
depth_max: vine.number().optional().requiredIfExists('depth_min'),
|
||||
depth_absolut: vine.number().negative().optional(),
|
||||
depth_min: vine.number().negative().optional().requiredIfExists('depth_max'),
|
||||
depth_max: vine.number().negative().optional().requiredIfExists('depth_min'),
|
||||
}),
|
||||
references: vine
|
||||
.array(
|
||||
|
@ -120,10 +134,13 @@ export const createDatasetValidator = vine.compile(
|
|||
// last step
|
||||
files: vine
|
||||
.array(
|
||||
vine.myfile({
|
||||
size: '512mb',
|
||||
extnames: extensions,
|
||||
}).filenameLength({ clientNameSizeLimit : 100 }),
|
||||
vine
|
||||
.myfile({
|
||||
size: '512mb',
|
||||
extnames: extensions,
|
||||
})
|
||||
.filenameLength({ clientNameSizeLimit: 100 })
|
||||
.fileScan({ removeInfected: true }),
|
||||
)
|
||||
.minLength(1),
|
||||
}),
|
||||
|
@ -175,7 +192,13 @@ export const updateDatasetValidator = vine.compile(
|
|||
authors: vine
|
||||
.array(
|
||||
vine.object({
|
||||
email: vine.string().trim().maxLength(255).email().normalizeEmail().isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
||||
email: vine
|
||||
.string()
|
||||
.trim()
|
||||
.maxLength(255)
|
||||
.email()
|
||||
.normalizeEmail()
|
||||
.isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
||||
first_name: vine.string().trim().minLength(3).maxLength(255),
|
||||
last_name: vine.string().trim().minLength(3).maxLength(255),
|
||||
}),
|
||||
|
@ -185,7 +208,13 @@ export const updateDatasetValidator = vine.compile(
|
|||
contributors: vine
|
||||
.array(
|
||||
vine.object({
|
||||
email: vine.string().trim().maxLength(255).email().normalizeEmail().isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
||||
email: vine
|
||||
.string()
|
||||
.trim()
|
||||
.maxLength(255)
|
||||
.email()
|
||||
.normalizeEmail()
|
||||
.isUniquePerson({ table: 'persons', column: 'email', idField: 'id' }),
|
||||
first_name: vine.string().trim().minLength(3).maxLength(255),
|
||||
last_name: vine.string().trim().minLength(3).maxLength(255),
|
||||
pivot_contributor_type: vine.enum(Object.keys(ContributorTypes)),
|
||||
|
@ -209,13 +238,13 @@ export const updateDatasetValidator = vine.compile(
|
|||
x_max: vine.number(),
|
||||
y_min: vine.number(),
|
||||
y_max: vine.number(),
|
||||
elevation_absolut: vine.number().optional(),
|
||||
elevation_min: vine.number().optional().requiredIfExists('elevation_max'),
|
||||
elevation_max: vine.number().optional().requiredIfExists('elevation_min'),
|
||||
elevation_absolut: vine.number().positive().optional(),
|
||||
elevation_min: vine.number().positive().optional().requiredIfExists('elevation_max'),
|
||||
elevation_max: vine.number().positive().optional().requiredIfExists('elevation_min'),
|
||||
// type: vine.enum(Object.values(DescriptionTypes)),
|
||||
depth_absolut: vine.number().optional(),
|
||||
depth_min: vine.number().optional().requiredIfExists('depth_max'),
|
||||
depth_max: vine.number().optional().requiredIfExists('depth_min'),
|
||||
depth_absolut: vine.number().negative().optional(),
|
||||
depth_min: vine.number().negative().optional().requiredIfExists('depth_max'),
|
||||
depth_max: vine.number().negative().optional().requiredIfExists('depth_min'),
|
||||
}),
|
||||
references: vine
|
||||
.array(
|
||||
|
@ -238,14 +267,13 @@ export const updateDatasetValidator = vine.compile(
|
|||
.minLength(3)
|
||||
.distinct('value'),
|
||||
// last step
|
||||
files: vine
|
||||
.array(
|
||||
vine.myfile({
|
||||
size: '512mb',
|
||||
extnames: extensions,
|
||||
}),
|
||||
),
|
||||
// .minLength(1),
|
||||
files: vine.array(
|
||||
vine.myfile({
|
||||
size: '512mb',
|
||||
extnames: extensions,
|
||||
}),
|
||||
),
|
||||
// .minLength(1),
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue