meilisearch
Version:
The Meilisearch JS client for Node.js and the browser.
155 lines (135 loc) • 4.39 kB
text/typescript
import { Config, TokenSearchRules, TokenOptions } from './types'
import crypto from 'crypto'
import { MeiliSearchError } from './errors'
import { validateUuid4 } from './utils'
function encode64(data: any) {
return Buffer.from(JSON.stringify(data)).toString('base64')
}
/**
* Create the header of the token.
*
* @param apiKey - API key used to sign the token.
* @param encodedHeader - Header of the token in base64.
* @param encodedPayload - Payload of the token in base64.
* @returns The signature of the token in base64.
*/
function sign(apiKey: string, encodedHeader: string, encodedPayload: string) {
return crypto
.createHmac('sha256', apiKey)
.update(`${encodedHeader}.${encodedPayload}`)
.digest('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '')
}
/**
* Create the header of the token.
*
* @returns The header encoded in base64.
*/
function createHeader() {
const header = {
alg: 'HS256',
typ: 'JWT',
}
return encode64(header).replace(/=/g, '')
}
/**
* Validate the parameter used for the payload of the token.
*
* @param searchRules - Search rules that are applied to every search.
* @param apiKey - Api key used as issuer of the token.
* @param uid - The uid of the api key used as issuer of the token.
* @param expiresAt - Date at which the token expires.
*/
function validateTokenParameters(tokenParams: {
searchRules: TokenSearchRules
uid: string
apiKey: string
expiresAt?: Date
}) {
const { searchRules, uid, apiKey, expiresAt } = tokenParams
if (expiresAt) {
if (!(expiresAt instanceof Date)) {
throw new MeiliSearchError(
`Meilisearch: The expiredAt field must be an instance of Date.`
)
} else if (expiresAt.getTime() < Date.now()) {
throw new MeiliSearchError(
`Meilisearch: The expiresAt field must be a date in the future.`
)
}
}
if (searchRules) {
if (!(typeof searchRules === 'object' || Array.isArray(searchRules))) {
throw new MeiliSearchError(
`Meilisearch: The search rules added in the token generation must be of type array or object.`
)
}
}
if (!apiKey || typeof apiKey !== 'string') {
throw new MeiliSearchError(
`Meilisearch: The API key used for the token generation must exist and be of type string.`
)
}
if (!uid || typeof uid !== 'string') {
throw new MeiliSearchError(
`Meilisearch: The uid of the api key used for the token generation must exist, be of type string and comply to the uuid4 format.`
)
}
if (!validateUuid4(uid)) {
throw new MeiliSearchError(
`Meilisearch: The uid of your key is not a valid uuid4. To find out the uid of your key use getKey().`
)
}
}
/**
* Create the payload of the token.
*
* @param searchRules - Search rules that are applied to every search.
* @param uid - The uid of the api key used as issuer of the token.
* @param expiresAt - Date at which the token expires.
* @returns The payload encoded in base64.
*/
function createPayload(payloadParams: {
searchRules: TokenSearchRules
uid: string
expiresAt?: Date
}): string {
const { searchRules, uid, expiresAt } = payloadParams
const payload = {
searchRules,
apiKeyUid: uid,
exp: expiresAt ? Math.floor(expiresAt.getTime() / 1000) : undefined,
}
return encode64(payload).replace(/=/g, '')
}
class Token {
config: Config
constructor(config: Config) {
this.config = config
}
/**
* Generate a tenant token
*
* @param apiKeyUid - The uid of the api key used as issuer of the token.
* @param searchRules - Search rules that are applied to every search.
* @param options - Token options to customize some aspect of the token.
* @returns The token in JWT format.
*/
generateTenantToken(
apiKeyUid: string,
searchRules: TokenSearchRules,
options?: TokenOptions
): string {
const apiKey = options?.apiKey || this.config.apiKey || ''
const uid = apiKeyUid || ''
const expiresAt = options?.expiresAt
validateTokenParameters({ apiKey, uid, expiresAt, searchRules })
const encodedHeader = createHeader()
const encodedPayload = createPayload({ searchRules, uid, expiresAt })
const signature = sign(apiKey, encodedHeader, encodedPayload)
return `${encodedHeader}.${encodedPayload}.${signature}`
}
}
export { Token }