-
Notifications
You must be signed in to change notification settings - Fork 85
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add generate tenant token feature (#1162)
* Add generate tenant token feature * Change searchClient class to just Client * Change searchClient class to just Client * Add generateToken in parent Client as fallback if it does not exists * Add tests on token generation * Fix linting and types errors * Change generate token to sync method instead of async * Remove unecessary crypto inport in rollup build * Add crypto dependency in external packages in rollup * Update esm env with node target * Update comments * Update comments * Remove unecessary comments * Add token payload validation * Fix node env test * Improve variable naming of encoding function
- Loading branch information
Showing
16 changed files
with
453 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,32 @@ | ||
import { Client } from './client' | ||
import { Config } from '../../types' | ||
import { Config, TokenSearchRules, TokenOptions } from '../../types' | ||
import { Token } from '../token' | ||
|
||
class MeiliSearch extends Client { | ||
tokens: Token | ||
|
||
constructor(config: Config) { | ||
super(config) | ||
this.tokens = new Token(config) | ||
} | ||
|
||
/** | ||
* Generate a tenant token | ||
* | ||
* @memberof MeiliSearch | ||
* @method generateTenantToken | ||
* @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( | ||
searchRules: TokenSearchRules, | ||
options?: TokenOptions | ||
): string { | ||
if (typeof window === 'undefined') { | ||
return this.tokens.generateTenantToken(searchRules, options) | ||
} | ||
return super.generateTenantToken(searchRules, options) | ||
} | ||
} | ||
export { MeiliSearch } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import { Config, TokenSearchRules, TokenOptions } from '../types' | ||
import crypto from 'crypto' | ||
|
||
function encode64(data: any) { | ||
return Buffer.from(JSON.stringify(data)).toString('base64') | ||
} | ||
|
||
/** | ||
* Create the header of the token. | ||
* | ||
* @param {String} apiKey API key used to sign the token. | ||
* @param {String} encodedHeader Header of the token in base64. | ||
* @param {String} encodedPayload Payload of the token in base64. | ||
* @returns {String} 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 {String} 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} searchRules Search rules that are applied to every search. | ||
* @param {String} apiKey Api key used as issuer of the token. | ||
* @param {Date | undefined} expiresAt Date at which the token expires. | ||
*/ | ||
function validatePayload(payloadParams: { | ||
searchRules: TokenSearchRules | ||
apiKey: string | ||
expiresAt?: Date | ||
}) { | ||
const { searchRules, apiKey, expiresAt } = payloadParams | ||
const error = new Error() | ||
|
||
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 (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}.` | ||
) | ||
} | ||
} | ||
|
||
if (!apiKey || typeof apiKey !== 'string') { | ||
throw new Error( | ||
`Meilisearch: The API key used for the token generation must exist and be of type string. \n ${error.stack}.` | ||
) | ||
} | ||
} | ||
|
||
/** | ||
* 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 {Date | undefined} expiresAt Date at which the token expires. | ||
* @returns {String} The payload encoded in base64. | ||
*/ | ||
function createPayload(payloadParams: { | ||
searchRules: TokenSearchRules | ||
apiKey: string | ||
expiresAt?: Date | ||
}): string { | ||
const { searchRules, apiKey, expiresAt } = payloadParams | ||
validatePayload(payloadParams) | ||
const payload = { | ||
searchRules, | ||
apiKeyPrefix: apiKey.substring(0, 8), | ||
exp: expiresAt?.getTime(), | ||
} | ||
|
||
return encode64(payload).replace(/=/g, '') | ||
} | ||
|
||
class Token { | ||
config: Config | ||
|
||
constructor(config: Config) { | ||
this.config = config | ||
} | ||
|
||
/** | ||
* Generate a tenant token | ||
* | ||
* @memberof MeiliSearch | ||
* @method generateTenantToken | ||
* @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( | ||
searchRules: TokenSearchRules, | ||
options?: TokenOptions | ||
): string { | ||
const apiKey = options?.apiKey || this.config.apiKey || '' | ||
const expiresAt = options?.expiresAt | ||
|
||
const encodedHeader = createHeader() | ||
const encodedPayload = createPayload({ searchRules, apiKey, expiresAt }) | ||
const signature = sign(apiKey, encodedHeader, encodedPayload) | ||
|
||
return `${encodedHeader}.${encodedPayload}.${signature}` | ||
} | ||
} | ||
export { Token } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import { MeiliSearch } from '../meilisearch.esm' | ||
import * as DefaultMeiliSearch from '../meilisearch.esm' | ||
import { MeiliSearch } from '../../../../' | ||
import * as DefaultMeiliSearch from '../../../../' | ||
|
||
const client = new MeiliSearch({ host:'http://localhost:7700', apiKey: 'masterKey'}) | ||
const defaultClient = new DefaultMeiliSearch.MeiliSearch({ host:'http://localhost:7700', apiKey: 'masterKey'}) | ||
console.log({ client, defaultClient }) | ||
const token = client.generateTenantToken([]) | ||
console.log({ client, token, defaultClient }) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,10 @@ | ||
const { MeiliSearch } = require('../../../dist/bundles/meilisearch.cjs.js') | ||
const DefaultMeiliSearch = require('../../../dist/bundles/meilisearch.cjs.js') | ||
const { MeiliSearch } = require('../../../') | ||
const DefaultMeiliSearch = require('../../../') | ||
|
||
const CJStest = new MeiliSearch({ host:'http://localhost:7700', masterKey: 'masterKey'}) | ||
const DefaultCJSTest = new DefaultMeiliSearch.MeiliSearch({ host:'http://localhost:7700', masterKey: 'masterKey'}) | ||
console.log({ CJStest, DefaultCJSTest }) | ||
const CJStest = new MeiliSearch({ host:'http://localhost:7700', apiKey: 'masterKey'}) | ||
const DefaultCJSTest = new DefaultMeiliSearch.MeiliSearch({ host:'http://localhost:7700', apiKey: 'masterKey'}) | ||
|
||
DefaultCJSTest.generateTenantToken([]) // Resolved using the `main` field | ||
CJStest.generateTenantToken([]) // Resolved using the `main` field | ||
|
||
console.log({ CJStest, DefaultCJSTest }) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.