Hashing
O módulo Hash do AdonisJS permite que você faça hash dos valores usando bcrypt
, argon2
ou scrypt
junto com a opção de adicionar um driver de hash personalizado.
Você pode configurar o driver de sua escolha dentro do arquivo config/hash.ts
.
import { hashConfig } from '@adonisjs/core/build/config'
export default hashConfig({
default: Env.get('HASH_DRIVER', 'scrypt'),
list: {
scrypt: {
driver: 'scrypt',
cost: 16384,
blockSize: 8,
parallelization: 1,
saltSize: 16,
keyLength: 64,
maxMemory: 32 * 1024 * 1024,
},
/**
* Make sure to install the driver from npm
* ------------------------------------
* npm i phc-argon2
* ------------------------------------
*/
argon: {
driver: 'argon2',
variant: 'id',
iterations: 3,
memory: 4096,
parallelism: 1,
saltSize: 16,
},
/**
* Make sure to install the driver from npm
* ------------------------------------
* npm i phc-bcrypt
* ------------------------------------
*/
bcrypt: {
driver: 'bcrypt',
rounds: 10,
},
},
})
Hasher padrão
A propriedade default
configura o hasher para usar por padrão para valores de hash. Deve ser um dos hashers disponíveis do objeto de lista.
Hashers disponíveis
O objeto list
contém um ou mais hashers disponíveis para serem usados para valores de hash. Um hasher deve usar um dos drivers disponíveis.
Método Node.js cryto.scrypt
para gerar e verificar hashes.
- O hasher
argon
usa o driverargon2
. Você terá que instalar o seguinte pacote para usar o argon. Se você não tiver nenhuma preferência forte, recomendamos que use o argon na produçãobashnpm i phc-argon2
- O hasher
bcrypt
usa o driverbcrypt
. Você terá que instalar o seguinte pacote para usar o bcrypt.bashnpm i phc-bcrypt
Valores de hash
make
O método Hash.make
aceita um valor de string para um hash.
import Hash from '@ioc:Adonis/Core/Hash'
const hashedPassword = await Hash.make(user.password)
Na maioria das vezes, você estará fazendo o hash da senha do usuário, então é melhor usar um gancho de modelo para executar o hash.
import Hash from '@ioc:Adonis/Core/Hash'
import { column, beforeSave, BaseModel } from '@ioc:Adonis/Lucid/Orm'
export default class User extends BaseModel {
@column({ isPrimary: true })
public id: number
@column({ serializeAs: null })
public password: string
@beforeSave()
public static async hashPassword(user: User) {
if (user.$dirty.password) {
user.password = await Hash.make(user.password)
}
}
}
verify
Você não pode converter valores com hash de volta para uma string simples, e você só pode verificar se uma determinada string de texto simples corresponde a um determinado hash.
if (await Hash.verify(hashedValue, plainTextValue)) {
// verified
}
needsReHash
Descubra se um valor com hash anterior precisa de um rehash. Este método retorna true se o fator de trabalho usado pelo hasher mudou desde que a senha foi hash.
O melhor momento para verificar needsReHash
é geralmente durante o login do usuário.
if (Hash.needsReHash(user.password)) {
// Você terá que ajustar o gancho do modelo para não
// refazer o hash da senha já hash
user.password = await Hash.make(plainPassword)
}
Formato de string PHC
Nossos drivers retornam a saída de hash pelo formato de string PHC. Ele nos permite verificar os hashes em relação à configuração atual de um hasher e decidir se o hash precisa ser refeito ou não.
Adicionando um driver personalizado
O módulo Hash é extensível e permite que você registre seus próprios drivers personalizados. Cada driver deve implementar a seguinte interface HashDriverContract
:
interface HashDriverContract {
ids?: string[]
params?: any
/**
* Hash de valor de texto simples usando o mapeamento padrão
*/
make(value: string): Promise<string>
/**
* Verifique o hash em relação à configuração atual para descobrir
* se ele precisa ser refeito ou não
*/
needsReHash?(hashedValue: string): boolean
/**
* Verifique o valor simples em relação ao valor com hash para descobrir se é
* válido ou não
*/
verify(hashedValue: string, plainValue: string): Promise<boolean>
}
make
O método make
é responsável por fazer o hash do valor da string simples.
verify
O método verify
é responsável por verificar a string simples em relação a um hash pré-existente.
needsReHash
O needsReHash
é opcional. No entanto, ele deve ser implementado se seu algoritmo de hash tiver suporte para ele.
params
/ids
As propriedades params
e ids
são algo que você precisa ao usar o formato de string PHC. Basta verificar a implementação do driver existente e ler sobre o formato de string PHC para aprender mais sobre ele.
Estendendo de fora para dentro
Sempre que você estiver estendendo o núcleo do framework. É melhor assumir que você não tem acesso ao código do aplicativo e suas dependências. Em outras palavras, escreva suas extensões como se estivesse escrevendo um pacote de terceiros e use injeção de dependência para depender de outras dependências.
Para fins de demonstração, vamos criar um driver hash fictício:
mkdir providers/HashDriver
touch providers/HashDriver/index.ts
A estrutura do diretório será semelhante à seguinte.
providers
└── HashDriver
└── index.ts
Abra o arquivo HashDriver/index.ts
e cole o seguinte conteúdo dentro dele.
// providers/HashDriver/index.ts
import { HashDriverContract } from '@ioc:Adonis/Core/Hash'
export class PlainTextDriver implements HashDriverContract {
public async make(value: string) {
return value
}
public async verify(hashedValue: string, plainValue: string) {
return hashedValue === plainValue
}
}
Finalmente, abra o arquivo providers/AppProvider.ts
e adicione o driver personalizado dentro do método boot
.
import { ApplicationContract } from '@ioc:Adonis/Core/Application'
export default class AppProvider {
public static needsApplication = true
constructor(protected app: ApplicationContract) {}
public async boot() {
const { PlainTextDriver } = await import('./HashDriver')
const Hash = this.app.container.use('Adonis/Core/Hash')
Hash.extend('plainText', () => {
return new PlainTextDriver()
})
}
}
Voilá! Seu driver PlainTextDriver
está pronto para ser usado.
Informando o TypeScript sobre o novo driver
Antes que alguém possa referenciar este driver dentro do arquivo config/hash.ts
. Você terá que informar o compilador estático do TypeScript sobre sua existência.
Se você estiver criando um pacote, então você pode escrever o seguinte código dentro do arquivo principal do seu pacote, caso contrário você pode escrevê-lo dentro do arquivo contracts/hash.ts
.
import { PlainTextDriver } from '../providers/HashDriver'
declare module '@ioc:Adonis/Core/Hash' {
interface HashDrivers {
plainText: {
config: {
driver: 'plainText',
// ...resto da configuração
}
implementation: PlainTextDriver
}
}
}
Atualizando a configuração
Para usar o driver, você terá que definir um mapeamento dentro do arquivo de configuração definindo driver=plainText
.
// config/hash.ts
list: {
myHashDriver: {
driver: 'plainText',
},
// outros hashers
}
Agora, você pode usar o mapeamento recém-definido da seguinte forma.
await Hash.use('myHashDriver').make('foo')