Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update token creation v0.28.0 #1268

Merged
merged 12 commits into from
Jun 27, 2022
3 changes: 3 additions & 0 deletions src/clients/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,11 +390,14 @@ class Client {
*
* @memberof MeiliSearch
* @method generateTenantToken
* @param {apiKeyUid} apiKeyUid The uid of the api key used as issuer of the token.
* @param {SearchRules} searchRules Search rules that are applied to every search.
* @param {TokenOptions} options Token options to customize some aspect of the token.
*
* @returns {String} The token in JWT format.
*/
generateTenantToken(
_apiKeyUid: string,
_searchRules: TokenSearchRules,
_options?: TokenOptions
): string {
Expand Down
7 changes: 5 additions & 2 deletions src/clients/node-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,21 @@ class MeiliSearch extends Client {
*
* @memberof MeiliSearch
* @method generateTenantToken
* @param {apiKeyUid} apiKeyUid The uid of the api key used as issuer of the token.
* @param {SearchRules} searchRules Search rules that are applied to every search.
* @param {TokenOptions} options Token options to customize some aspect of the token.
*
* @returns {String} The token in JWT format.
*/
generateTenantToken(
apiKeyUid: string,
searchRules: TokenSearchRules,
options?: TokenOptions
): string {
if (typeof window === 'undefined') {
return this.tokens.generateTenantToken(searchRules, options)
return this.tokens.generateTenantToken(apiKeyUid, searchRules, options)
}
return super.generateTenantToken(searchRules, options)
return super.generateTenantToken(apiKeyUid, searchRules, options)
}
}
export { MeiliSearch }
55 changes: 39 additions & 16 deletions src/token.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
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')
Expand Down Expand Up @@ -42,35 +44,50 @@ function createHeader() {
*
* @param {SearchRules} searchRules Search rules that are applied to every search.
* @param {String} apiKey Api key used as issuer of the token.
* @param {String} uid The uid of the api key used as issuer of the token.
* @param {Date | undefined} expiresAt Date at which the token expires.
*/
function validatePayload(payloadParams: {
function validateTokenParameters(tokenParams: {
searchRules: TokenSearchRules
uid: string
apiKey: string
expiresAt?: Date
}) {
const { searchRules, apiKey, expiresAt } = payloadParams
const error = new Error()
const { searchRules, uid, apiKey, expiresAt } = tokenParams

if (expiresAt) {
if (!(expiresAt instanceof Date) || expiresAt.getTime() < Date.now()) {
throw new Error(
`Meilisearch: When the expiresAt field in the token generation has a value, it must be a date set in the future and not in the past. \n ${error.stack}.`
if (!(expiresAt instanceof Date)) {
throw new MeiliSearchError(
`Meilisearch: The expiredAt field must be an instance of Date.\n`
bidoubiwa marked this conversation as resolved.
Show resolved Hide resolved
)
} else if (expiresAt.getTime() < Date.now()) {
bidoubiwa marked this conversation as resolved.
Show resolved Hide resolved
throw new MeiliSearchError(`Meilisearch: The token has expired.\n`)
}
}

if (searchRules) {
if (!(typeof searchRules === 'object' || Array.isArray(searchRules))) {
throw new Error(
`Meilisearch: The search rules added in the token generation must be of type array or object. \n ${error.stack}.`
throw new MeiliSearchError(
`Meilisearch: The search rules added in the token generation must be of type array or object.\n`
bidoubiwa marked this conversation as resolved.
Show resolved Hide resolved
)
}
}

if (!apiKey || typeof apiKey !== 'string') {
bidoubiwa marked this conversation as resolved.
Show resolved Hide resolved
throw new Error(
`Meilisearch: The API key used for the token generation must exist and be of type string. \n ${error.stack}.`
throw new MeiliSearchError(
`Meilisearch: The API key used for the token generation must exist and be of type string.\n`
)
}

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.\n`
)
}

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().\n`
)
}
}
Expand All @@ -79,20 +96,20 @@ function validatePayload(payloadParams: {
* Create the payload of the token.
*
* @param {SearchRules} searchRules Search rules that are applied to every search.
* @param {String} apiKey Api key used as issuer of the token.
* @param {String} uid The uid of the api key used as issuer of the token.
* @param {Date | undefined} expiresAt Date at which the token expires.
* @returns {String} The payload encoded in base64.
*/
function createPayload(payloadParams: {
searchRules: TokenSearchRules
apiKey: string
uid: string
expiresAt?: Date
}): string {
const { searchRules, apiKey, expiresAt } = payloadParams
validatePayload(payloadParams)
const { searchRules, uid, expiresAt } = payloadParams

const payload = {
searchRules,
apiKeyPrefix: apiKey.substring(0, 8),
apiKeyUid: uid,
exp: expiresAt?.getTime(),
}

Expand All @@ -111,19 +128,25 @@ class Token {
*
* @memberof MeiliSearch
* @method generateTenantToken
* @param {apiKeyUid} apiKeyUid The uid of the api key used as issuer of the token.
* @param {SearchRules} searchRules Search rules that are applied to every search.
* @param {TokenOptions} options Token options to customize some aspect of the token.
*
* @returns {String} 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, apiKey, expiresAt })
const encodedPayload = createPayload({ searchRules, uid, expiresAt })
const signature = sign(apiKey, encodedHeader, encodedPayload)

return `${encodedHeader}.${encodedPayload}.${signature}`
Expand Down
6 changes: 6 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,15 @@ function addTrailingSlash(url: string): string {
return url
}

function validateUuid4(uuid: string): boolean {
const regexExp = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/gi
return regexExp.test(uuid)
}

export {
sleep,
removeUndefinedFromObject,
addProtocolIfNotPresent,
addTrailingSlash,
validateUuid4,
}
Loading