Skip to content
Merged
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
7 changes: 4 additions & 3 deletions examples/nodejs/access-control/access-control.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,16 @@ async function main() {
try {
await set(mainCacheClient, CACHE_OPEN_DOOR, 'hello', 'world');

// Create a token valid for 600 seconds that can only read a specific cache 'open-door'
const [scopedToken, scopedRefreshToken] = await generateApiKey(
// Create a scoped API key valid for 600 seconds that can only read a specific cache 'open-door'.
// Note: generateApiKey() produces a legacy v1 API key, hence why the deprecated fromString() method is used.
const [scopedApiKey, scopedRefreshToken] = await generateApiKey(
mainAuthClient,
PermissionScopes.cacheReadOnly(CACHE_OPEN_DOOR),
tokenValidForSeconds
);
const scopedTokenCacheClient = await CacheClient.create({
configuration: Configurations.Laptop.v1(),
credentialProvider: CredentialProvider.fromDisposableToken({apiKey: scopedToken}),
credentialProvider: CredentialProvider.fromString(scopedApiKey),
defaultTtlSeconds: 600,
});

Expand Down
14 changes: 11 additions & 3 deletions examples/nodejs/cache/doc-example-files/doc-examples-js-apis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,14 @@ function retrieveApiKeyV2FromYourSecretsManager(): string {
return 'eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJ0IjoiZyIsImp0aSI6InNvbWUtaWQifQ.GMr9nA6HE0ttB6llXct_2Sg5-fOKGFbJCdACZFgNbN1fhT6OPg_hVc8ThGzBrWC_RlsBpLA1nzqK3SOJDXYxAw';
}

function retrieveDisposableTokenFromYourTokenVendingMachine(): string {
// this is not a valid disposable token but conforms to the syntax requirements.
// In practice, disposable tokens are fetched from a token vending machine backed by AuthClient.generateDisposableToken().
const fakeDisposableToken =
'eyJhcGlfa2V5IjogImV5SjBlWEFpT2lKS1YxUWlMQ0poYkdjaU9pSklVekkxTmlKOS5leUpwYzNNaU9pSlBibXhwYm1VZ1NsZFVJRUoxYVd4a1pYSWlMQ0pwWVhRaU9qRTJOemd6TURVNE1USXNJbVY0Y0NJNk5EZzJOVFV4TlRReE1pd2lZWFZrSWpvaUlpd2ljM1ZpSWpvaWFuSnZZMnRsZEVCbGVHRnRjR3hsTG1OdmJTSjkuOEl5OHE4NExzci1EM1lDb19IUDRkLXhqSGRUOFVDSXV2QVljeGhGTXl6OCIsICJlbmRwb2ludCI6ICJ0ZXN0Lm1vbWVudG9ocS5jb20ifQo=';
return fakeDisposableToken;
}

function example_API_CredentialProviderFromEnvVar() {
CredentialProvider.fromEnvVar('MOMENTO_API_KEY');
}
Expand All @@ -144,8 +152,8 @@ function example_API_CredentialProviderFromApiKeyV2() {
}

function example_API_CredentialProviderFromDisposableToken() {
const apiKey = retrieveApiKeyFromYourSecretsManager();
CredentialProvider.fromDisposableToken({apiKey: apiKey});
const disposableToken = retrieveDisposableTokenFromYourTokenVendingMachine();
CredentialProvider.fromDisposableToken(disposableToken);
}

function example_API_CredentialProviderFromString() {
Expand Down Expand Up @@ -2044,7 +2052,7 @@ async function main() {

example_API_InstantiateAuthClient();
const authClient = new AuthClient({
credentialProvider: CredentialProvider.fromEnvironmentVariable('V1_API_KEY'),
credentialProvider: CredentialProvider.fromEnvVarV2(),
});
await example_API_GenerateApiKey(authClient);
await example_API_RefreshApiKey(authClient);
Expand Down
2 changes: 1 addition & 1 deletion examples/nodejs/topics/topic-refresh-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ function getRefreshAfterMs(expiresAt: ExpiresAt, refreshBefore: number): number
// Helper function to get a disposable token from the auth service
async function getDisposableToken(): Promise<{token: string; expiresAt: ExpiresAt}> {
const authClient = new AuthClient({
credentialProvider: CredentialProvider.fromEnvironmentVariable('V1_API_KEY'),
credentialProvider: CredentialProvider.fromEnvVarV2(),
});
const fetchResp = await authClient.generateDisposableToken(
DisposableTokenScopes.topicPublishSubscribe(AllCaches, AllTopics),
Expand Down
2 changes: 1 addition & 1 deletion examples/web/cache/README.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Run the example using:

```bash
# Run example code
V1_API_KEY=<YOUR V1 API KEY> npm run tokens
MOMENTO_API_KEY=<YOUR API KEY> MOMENTO_ENDPOINT=<YOUR ENDPOINT> npm run tokens
```

If you have deployed a token vending machine to generate disposable tokens like so:
Expand Down
2 changes: 1 addition & 1 deletion examples/web/cache/refresh-disposable-tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

async function localTokenVendingMachine(): Promise<{token: string; expiresAt: ExpiresAt}> {
const authClient = new AuthClient({
credentialProvider: CredentialProvider.fromEnvVar('V1_API_KEY'),
credentialProvider: CredentialProvider.fromEnvVarV2(),
});
const tokenResponse = await authClient.generateDisposableToken(
DisposableTokenScopes.topicPublishSubscribe('my-cache', AllTopics),
Expand All @@ -41,7 +41,7 @@
return disposableToken;
}

async function tokenVendingMachine(): Promise<{token: string; expiresAt: ExpiresAt}> {

Check warning on line 44 in examples/web/cache/refresh-disposable-tokens.ts

View workflow job for this annotation

GitHub Actions / Test web examples on node 18

'tokenVendingMachine' is defined but never used
const resp = await fetch(process.env.TVM_ENDPOINT as string);
const respJson = (await resp.json()) as {authToken: string; expiresAt: number};
const disposableToken = {
Expand Down
4 changes: 1 addition & 3 deletions examples/web/nextjs-chat/src/app/api/momento/token/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ import { getServerSession } from "next-auth";
import { type NextRequest } from "next/server";

const authClient = new AuthClient({
credentialProvider: CredentialProvider.fromString({
apiKey: process.env.V1_API_KEY,
}),
credentialProvider: CredentialProvider.fromEnvVarV2(),
});

export const revalidate = 0;
Expand Down
4 changes: 1 addition & 3 deletions examples/web/nextjs-chat/src/utils/momento-web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ async function getNewWebClients(): Promise<MomentoClients> {
const token = await fetchResp.text();
const topicClient = new TopicClient({
configuration: Configurations.Browser.v1(),
credentialProvider: CredentialProvider.fromDisposableToken({
apiKey: token,
}),
credentialProvider: CredentialProvider.fromDisposableToken(token),
});
webTopicClient = topicClient;
return {
Expand Down
42 changes: 34 additions & 8 deletions packages/core/src/auth/credential-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ interface CredentialProviderProps {
*/
export abstract class CredentialProvider {
/**
* @returns {string} Auth token provided by user, required to authenticate with the service
* @returns {string} The raw credential value used to authenticate with the service. This will be either a
* v2 API key (for long-lived credentials created via the Momento console) or a disposable token (for
* short-lived credentials created via AuthClient.generateDisposableToken).
*/
abstract getAuthToken(): string;

Expand Down Expand Up @@ -98,7 +100,8 @@ export abstract class CredentialProvider {
abstract isEndpointSecure(): boolean;

/**
* @deprecated use fromEnvVarV2() instead
* @deprecated use fromEnvVarV2() instead. This method only supports legacy JWT tokens and v1 api keys.
* Use fromEnvVarV2() for current v2 API keys.
*/
static fromEnvironmentVariable(
props: EnvMomentoTokenProviderProps | string
Expand All @@ -107,7 +110,8 @@ export abstract class CredentialProvider {
}

/**
* @deprecated use fromEnvVarV2() instead
* @deprecated use fromEnvVarV2() instead. This method only supports legacy JWT tokens and v1 api keys.
* Use fromEnvVarV2() for current v2 API keys.
*/
static fromEnvVar(
props: EnvMomentoTokenProviderProps | string
Expand All @@ -116,26 +120,43 @@ export abstract class CredentialProvider {
}

/**
* @deprecated use fromApiKeyV2() or fromDisposableToken() instead
* @deprecated use fromApiKeyV2() for long-lived v2 API keys or fromDisposableToken() for short-lived
* disposable tokens instead.
*/
static fromString(
props: StringMomentoTokenProviderProps | string
): CredentialProvider {
return new StringMomentoTokenProvider(props);
}

/**
* Creates a CredentialProvider from a short-lived disposable token string. Disposable tokens are generated
* via AuthClient.generateDisposableToken() and has a mandatory expiration time. They are intended for browser and
* client-side use where embedding a long-lived API key would be insecure.
*
* Do NOT use this method with a long-lived v2 API key — use fromEnvVarV2() or fromApiKeyV2() instead.
*/
static fromDisposableToken(
props: StringMomentoTokenProviderProps | string
): CredentialProvider {
return new StringMomentoTokenProvider(props);
}

/**
* Creates a CredentialProvider by reading a v2 API key and service endpoint from environment variables
* MOMENTO_API_KEY and MOMENTO_ENDPOINT, respectively.
* V2 API keys are created via the Momento console.
*/
static fromEnvVarV2(
props?: EnvMomentoV2TokenProviderProps
): CredentialProvider {
return new EnvMomentoV2TokenProvider(props);
}

/**
* Creates a CredentialProvider from a v2 API key string and service endpoint string.
* V2 API keys are created via the Momento console.
*/
static fromApiKeyV2(props: ApiKeyV2TokenProviderProps): CredentialProvider {
return new ApiKeyV2TokenProvider(props);
}
Expand Down Expand Up @@ -210,10 +231,13 @@ export interface EnvMomentoV2TokenProviderProps {
}

/**
* Reads and parses a momento auth token stored in a String
* Reads and parses a legacy credential stored as a string. Supports the base64-encoded
* format used by disposable tokens and legacy v1 api keys. Does NOT
* support v2 API keys -— use ApiKeyV2TokenProvider for those.
* @export
* @class StringMomentoTokenProvider
* @deprecated use ApiKeyV2TokenProvider instead
* @deprecated use ApiKeyV2TokenProvider for v2 API keys; use this class only for disposable tokens
* via CredentialProvider.fromDisposableToken()
*/
export class StringMomentoTokenProvider extends CredentialProviderBase {
private readonly apiKey: string;
Expand Down Expand Up @@ -332,10 +356,12 @@ export interface EnvMomentoTokenProviderProps extends CredentialProviderProps {
}

/**
* Reads and parses a momento auth token stored as an environment variable.
* Reads a legacy credential from an environment variable. Supports the base64-encoded
* format used by disposable tokens and the legacy v1 api keys. Does NOT
* support v2 API keys -— use EnvMomentoV2TokenProvider for those.
* @export
* @class EnvMomentoTokenProvider
* @deprecated use EnvMomentoV2TokenProvider instead
* @deprecated use EnvMomentoV2TokenProvider (via CredentialProvider.fromEnvVarV2()) for v2 API keys
*/
export class EnvMomentoTokenProvider extends StringMomentoTokenProvider {
environmentVariableName: string;
Expand Down
34 changes: 21 additions & 13 deletions packages/core/src/internal/clients/auth/AbstractAuthClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ export abstract class AbstractAuthClient implements IAuthClient {
}

/**
* Generates a new API key, along with a refresh token to refresh the API key before expiry.
* Generates a new, scoped API key along with a refresh token to refresh it before expiry.
*
* **Important:** The API key returned by this method is in the legacy v1 format.
* V1 are keys are now deprecated and support for them may be removed in a future release.
*
* @param {PermissionScope} scope - controls the permissions that the new key will have
* @param {string} expiresIn - How long the API key should be valid for in epoch timestamp.
* @param {ExpiresIn} expiresIn - how long the API key should be valid for
* @returns {Promise<GenerateApiKey.Response>} -
* {@link GenerateApiKey.Success} containing the API key, refresh token, origin and epoch timestamp when token expires.
* If the API key never expires, then no refresh token will be returned and expires at timestamp will be infinite.
* {@link GenerateApiKey.Success} containing the API key, refresh token, endpoint and epoch timestamp when the key expires.
* If the key never expires, no refresh token will be returned and the expiry timestamp will be infinite.
* {@link GenerateApiKey.Error} on failure.
*/
public async generateApiKey(
Expand All @@ -48,13 +51,17 @@ export abstract class AbstractAuthClient implements IAuthClient {
}

/**
* Refreshes an API key. Returns a new API key and refresh token, that will be able to be refreshed again in the future.
* The new API key will be valid for the same length of time as the original key, starting from the time of refresh.
* The original api key will still work until its expired.
* Refreshes an API key generated by `generateApiKey`. Returns a new API key and refresh token.
* The new key will be valid for the same duration as the original, starting from the time of refresh.
* The original key remains valid until it expires.
*
* **Note:** Only API keys produced by `generateApiKey` (legacy v1 api keys) can be refreshed.
* Disposable tokens generated by `generateDisposableToken` cannot be refreshed — generate a new one
* when the old one expires.
*
* @param {string} refreshToken - Refresh token used to refresh the API key.
* @param {string} refreshToken - the refresh token from a previous `generateApiKey` or `refreshApiKey` response
* @returns {Promise<RefreshApiKey.Response>} -
* {@link RefreshApiKey.Success} containing the new API key, refresh token, origin and epoch timestamp when the API key expires.
* {@link RefreshApiKey.Success} containing the new API key, refresh token, endpoint and epoch timestamp when the key expires.
* {@link RefreshApiKey.Error} on failure.
*/
public async refreshApiKey(
Expand All @@ -73,13 +80,14 @@ export abstract class AbstractAuthClient implements IAuthClient {
}

/**
* Generates a new disposable, fine-grained access token.
* Generates a new disposable token with fine-grained access control.
* Disposable tokens are short-lived and cannot be refreshed.
*
* @param {DisposableTokenScope} scope - controls the permissions that the new token will have
* @param {string} expiresIn - How long the token is valid for in epoch timestamp.
* @param {DisposableTokenProps} disposableTokenProps - Additional properties for the API
* @param {ExpiresIn} expiresIn - how long the token is valid for
* @param {DisposableTokenProps} disposableTokenProps - additional properties, e.g. a tokenId for audit logging
* @returns {Promise<GenerateDisposableToken.Response>} -
* {@link GenerateDisposableToken.Success} containing the api token, origin and epoch timestamp when token expires.
* {@link GenerateDisposableToken.Success} containing the disposable token, endpoint and epoch timestamp when it expires.
* {@link GenerateDisposableToken.Error} on failure.
*/
public async generateDisposableToken(
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/internal/utils/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export interface V2Claims {
t: string;
}

/**
* The decoded structure of a base64-encoded credential. This format is used by both disposable tokens and (now legacy) v1 API keys. The format
* embeds the raw key and the Momento endpoint together in a single portable string.
*/
export interface Base64DecodedV1Token {
api_key: string;
endpoint: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/messages/responses/generate-api-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class Success extends BaseResponseSuccess implements IResponse {
}

/**
* Indicates that an error occurred during the generate api token request.
* Indicates that an error occurred during the generate api key request.
*
* This response object includes the following fields that you can use to determine
* how you would like to handle the error:
Expand Down
Loading