Skip to content
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
2 changes: 2 additions & 0 deletions lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ export const authorizedTenantsClaimName = 'tenants';
export const permissionsClaimName = 'permissions';
/** The key of the roles claims in the claims map either under tenant or top level */
export const rolesClaimName = 'roles';
/** The key of the scopes claims in the claims map */
export const scopesClaimName = 'scopes';
34 changes: 33 additions & 1 deletion lib/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { SdkFnWrapper } from '@descope/core-js-sdk';
import { authorizedTenantsClaimName, refreshTokenCookieName } from './constants';
import { authorizedTenantsClaimName, refreshTokenCookieName, scopesClaimName } from './constants';
import { AuthenticationInfo } from './types';

/**
Expand Down Expand Up @@ -86,3 +86,35 @@ export function getAuthorizationClaimItems(
export function isUserAssociatedWithTenant(authInfo: AuthenticationInfo, tenant: string): boolean {
return !!authInfo.token[authorizedTenantsClaimName]?.[tenant];
}

/**
* Get the scopes from the JWT token
* @param authInfo The parsed authentication info from the JWT
* @returns the scopes from the top-level claim
*/
export function getScopes(authInfo: AuthenticationInfo): string[] {
const value = authInfo.token[scopesClaimName];
return Array.isArray(value) ? value : [];
}

/**
* Check if the user has all the required scopes
* @param authInfo The parsed authentication info from the JWT
* @param scopes list of scopes to check for
* @returns true if user has all required scopes, false otherwise
*/
export function hasScopes(authInfo: AuthenticationInfo, scopes: string[]): boolean {
const userScopes = getScopes(authInfo);
return scopes.every((scope) => userScopes.includes(scope));
}

/**
* Get the scopes that match the required scopes
* @param authInfo The parsed authentication info from the JWT
* @param scopes list of scopes to match against
* @returns array of scopes that match the required scopes
*/
export function getMatchedScopes(authInfo: AuthenticationInfo, scopes: string[]): string[] {
const userScopes = getScopes(authInfo);
return scopes.filter((scope) => userScopes.includes(scope));
}
24 changes: 24 additions & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
permissionsClaimName,
refreshTokenCookieName,
rolesClaimName,
scopesClaimName,
sessionTokenCookieName,
} from './constants';
import fetch from './fetch-polyfill';
Expand All @@ -20,6 +21,8 @@ import {
getCookieValue,
isUserAssociatedWithTenant,
withCookie,
hasScopes,
getMatchedScopes,
} from './helpers';
import withManagement from './management';
import { AuthenticationInfo, RefreshAuthenticationInfo, VerifyOptions } from './types';
Expand Down Expand Up @@ -422,6 +425,26 @@ const nodeSdk = ({ authManagementKey, managementKey, publicKey, ...config }: Nod
const membership = getAuthorizationClaimItems(authInfo, rolesClaimName, tenant);
return roles.filter((role) => membership.includes(role));
},

/**
* Make sure that all given scopes exist on the parsed JWT top level claims
* @param authInfo JWT parsed info
* @param scopes list of scopes to make sure they exist on the JWT claims
* @returns true if all scopes exist, false otherwise
*/
validateScopes(authInfo: AuthenticationInfo, scopes: string[]): boolean {
return hasScopes(authInfo, scopes);
},

/**
* Retrieves the scopes from JWT top level claims that match the specified scopes list
* @param authInfo JWT parsed info containing the scopes
* @param scopes List of scopes to match against the JWT claims
* @returns An array of scopes that are both in the JWT claims and the specified list. Returns an empty array if no matches are found
*/
getMatchedScopes(authInfo: AuthenticationInfo, scopes: string[]): string[] {
return getMatchedScopes(authInfo, scopes);
},
};

return wrapWith(
Expand Down Expand Up @@ -464,6 +487,7 @@ const nodeSdk = ({ authManagementKey, managementKey, publicKey, ...config }: Nod

nodeSdk.RefreshTokenCookieName = refreshTokenCookieName;
nodeSdk.SessionTokenCookieName = sessionTokenCookieName;
nodeSdk.ScopesClaimName = scopesClaimName;
nodeSdk.DescopeErrors = descopeErrors;

export default nodeSdk;
Expand Down
1 change: 1 addition & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ interface Token {
sub?: string;
exp?: number;
iss?: string;
scopes?: string[];
[claim: string]: unknown;
}

Expand Down
Loading