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

Separate token specific code into its own export #1739

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .code-samples.meilisearch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,8 @@ authorization_header_1: |-
const client = new MeiliSearch({ host: 'http://localhost:7700', apiKey: 'masterKey' })
client.getKeys()
tenant_token_guide_generate_sdk_1: |-
import { generateTenantToken } from 'meilisearch/token'

const searchRules = {
patient_medical_records: {
filter: 'user_id = 1'
Expand All @@ -709,7 +711,7 @@ tenant_token_guide_generate_sdk_1: |-
const apiKeyUid = '85c3c2f9-bdd6-41f1-abd8-11fcf80e0f76'
const expiresAt = new Date('2025-12-20') // optional

const token = await client.generateTenantToken(apiKeyUid, searchRules, {
const token = await generateTenantToken(apiKeyUid, searchRules, {
apiKey: apiKey,
expiresAt: expiresAt,
})
Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
"import": "./dist/bundles/meilisearch.mjs",
"require": "./dist/bundles/meilisearch.cjs",
"default": "./dist/bundles/meilisearch.umd.js"
},
"./token": {
"types": "./dist/types/token.d.ts",
"import": "./dist/bundles/token.mjs",
"require": "./dist/bundles/token.cjs",
"default": "./dist/bundles/token.cjs"
}
},
"sideEffects": false,
Expand Down
57 changes: 38 additions & 19 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,22 @@ const PLUGINS = [
}),
];

const INDEX_INPUT = "src/index.ts";
const TOKEN_INPUT = "src/token.ts";

const INDEX_EXPORTS = pkg.exports["."];
const TOKEN_EXPORTS = pkg.exports["./token"];

module.exports = [
// browser-friendly UMD build
// Index
{
input: "src/browser.ts", // directory to transpilation of typescript
input: INDEX_INPUT,
output: {
name: "window",
extend: true,
file: getOutputFileName(
// will add .min. in filename if in production env
resolve(ROOT, pkg.jsdelivr),
resolve(ROOT, INDEX_EXPORTS.browser),
env === "production",
),
format: "umd",
Expand Down Expand Up @@ -67,36 +73,49 @@ module.exports = [
}),
// nodePolyfills
json(),
env === "production" ? terser() : {}, // will minify the file in production mode
env === "production" ? terser() : {},
],
},
{
input: INDEX_INPUT,
output: [
{
file: INDEX_EXPORTS.import,
exports: "named",
format: "es",
sourcemap: env === "production",
},
],
plugins: PLUGINS,
},
{
input: INDEX_INPUT,
output: {
file: INDEX_EXPORTS.require,
exports: "named",
format: "cjs",
sourcemap: env === "production",
},
plugins: PLUGINS,
},

// ES module (for bundlers) build.
// Token
{
input: "src/index.ts",
input: TOKEN_INPUT,
output: [
{
file: getOutputFileName(
resolve(ROOT, pkg.module),
env === "production",
),
file: TOKEN_EXPORTS.import,
exports: "named",
format: "es",
sourcemap: env === "production", // create sourcemap for error reporting in production mode
sourcemap: env === "production",
},
],
plugins: PLUGINS,
},
// Common JS build (Node).
// Compatible only in a nodeJS environment.
{
input: "src/index.ts",
input: TOKEN_INPUT,
output: {
file: getOutputFileName(
// will add .min. in filename if in production env
resolve(ROOT, pkg.main),
env === "production",
),
file: TOKEN_EXPORTS.require,
exports: "named",
format: "cjs",
sourcemap: env === "production", // create sourcemap for error reporting in production mode
Expand Down
7 changes: 0 additions & 7 deletions src/browser.ts

This file was deleted.

10 changes: 0 additions & 10 deletions src/clients/browser-client.ts

This file was deleted.

36 changes: 0 additions & 36 deletions src/clients/node-client.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export * from "./errors";
export * from "./indexes";
export * from "./enqueued-task";
export * from "./task";
import { MeiliSearch } from "./clients/node-client";
import { MeiliSearch } from "./meilisearch";

export { MeiliSearch, MeiliSearch as Meilisearch };
export default MeiliSearch;
39 changes: 6 additions & 33 deletions src/clients/client.ts → src/meilisearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Copyright: 2019, MeiliSearch
*/

import { Index } from "../indexes";
import { Index } from "./indexes";
import {
KeyCreation,
Config,
Expand All @@ -16,8 +16,6 @@ import {
Stats,
Version,
ErrorStatusCode,
TokenSearchRules,
TokenOptions,
TasksQuery,
WaitOptions,
KeyUpdate,
Expand All @@ -34,12 +32,12 @@ import {
MultiSearchResponse,
SearchResponse,
FederatedMultiSearchParams,
} from "../types";
import { HttpRequests } from "../http-requests";
import { TaskClient, Task } from "../task";
import { EnqueuedTask } from "../enqueued-task";
} from "./types";
import { HttpRequests } from "./http-requests";
import { TaskClient, Task } from "./task";
import { EnqueuedTask } from "./enqueued-task";

class Client {
export class MeiliSearch {
config: Config;
httpRequest: HttpRequests;
tasks: TaskClient;
Expand Down Expand Up @@ -470,29 +468,4 @@ class Client {

return new EnqueuedTask(task);
}

///
/// TOKENS
///

/**
* 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,
): Promise<string> {
const error = new Error();
error.message = `Meilisearch: failed to generate a tenant token. Generation of a token only works in a node environment \n ${error.stack}.`;

return Promise.reject(error);
}
}

export { Client };
90 changes: 39 additions & 51 deletions src/token.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Config, TokenSearchRules, TokenOptions } from "./types";
import { TokenSearchRules, TokenOptions } from "./types";
import { MeiliSearchError } from "./errors";
import { validateUuid4 } from "./utils";

Expand Down Expand Up @@ -51,14 +51,15 @@ function createHeader() {
* @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: {
function validateTokenParameters({
searchRules,
apiKeyUid,
expiresAt,
}: {
searchRules: TokenSearchRules;
uid: string;
apiKey: string;
apiKeyUid: string;
expiresAt?: Date;
}) {
const { searchRules, uid, apiKey, expiresAt } = tokenParams;

if (expiresAt) {
if (!(expiresAt instanceof Date)) {
throw new MeiliSearchError(
Expand All @@ -79,19 +80,13 @@ function validateTokenParameters(tokenParams: {
}
}

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") {
if (!apiKeyUid || typeof apiKeyUid !== "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)) {
if (!validateUuid4(apiKeyUid)) {
throw new MeiliSearchError(
`Meilisearch: The uid of your key is not a valid uuid4. To find out the uid of your key use getKey().`,
);
Expand All @@ -106,53 +101,46 @@ function validateTokenParameters(tokenParams: {
* @param expiresAt - Date at which the token expires.
* @returns The payload encoded in base64.
*/
function createPayload(payloadParams: {
function createPayload({
searchRules,
apiKeyUid,
expiresAt,
}: {
searchRules: TokenSearchRules;
uid: string;
apiKeyUid: string;
expiresAt?: Date;
}): string {
const { searchRules, uid, expiresAt } = payloadParams;

const payload = {
searchRules,
apiKeyUid: uid,
apiKeyUid,
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.
*/
async generateTenantToken(
apiKeyUid: string,
searchRules: TokenSearchRules,
options?: TokenOptions,
): Promise<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 = await sign(apiKey, encodedHeader, encodedPayload);
/**
* 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.
*/
export async function generateTenantToken(
apiKeyUid: string,
searchRules: TokenSearchRules,
{ apiKey, expiresAt }: TokenOptions,
): Promise<string> {
validateTokenParameters({ apiKeyUid, expiresAt, searchRules });

const encodedHeader = createHeader();
const encodedPayload = createPayload({
searchRules,
apiKeyUid,
expiresAt,
});
const signature = await sign(apiKey, encodedHeader, encodedPayload);

return `${encodedHeader}.${encodedPayload}.${signature}`;
}
return `${encodedHeader}.${encodedPayload}.${signature}`;
}
export { Token };
2 changes: 1 addition & 1 deletion src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,6 @@ export type TokenSearchRules =
| string[];

export type TokenOptions = {
apiKey?: string;
apiKey: string;
expiresAt?: Date;
};
Loading