diff --git a/package-lock.json b/package-lock.json index 3b5bf8a..f029029 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@kinde-oss/kinde-auth-nextjs", - "version": "2.0.0-alpha.3", + "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@kinde-oss/kinde-auth-nextjs", - "version": "2.0.0-alpha.3", + "version": "2.0.0", "dependencies": { "@kinde-oss/kinde-typescript-sdk": "^2.2.3", "cookie": "^0.5.0", diff --git a/package.json b/package.json index 2812901..cb4ddf2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kinde-oss/kinde-auth-nextjs", - "version": "2.0.0-alpha.2", + "version": "2.0.0", "description": "Kinde Auth SDK for NextJS", "main": "dist/cjs/index.js", "module": "dist/index.js", diff --git a/src/authMiddleware/authMiddleware.js b/src/authMiddleware/authMiddleware.js index ae284db..b7946cd 100644 --- a/src/authMiddleware/authMiddleware.js +++ b/src/authMiddleware/authMiddleware.js @@ -1,4 +1,3 @@ -import jwt_decode from 'jwt-decode'; import {NextResponse} from 'next/server'; import {config} from '../config/index'; import {isTokenValid} from '../utils/pageRouter/isTokenValid'; @@ -10,11 +9,10 @@ export function authMiddleware(request) { let isAuthenticated = false; const nextUrl = trimTrailingSlash(request.nextUrl.href); const logoutUrl = trimTrailingSlash(config.postLogoutRedirectURL); - const kinde_token = request.cookies.get('kinde_token'); + const kinde_token = request.cookies.get('access_token'); const isLogoutUrl = nextUrl === logoutUrl; if (kinde_token) { - const payload = jwt_decode(JSON.parse(kinde_token.value).access_token); isAuthenticated = true; } @@ -49,7 +47,6 @@ const handleMiddleware = async (req, options, onSuccess) => { config.redirectURL ) ); - response.headers.set('x-hello-from-middleware2', 'hello'); return response; } diff --git a/src/config/index.js b/src/config/index.js index df5ee7a..c34e563 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -1,22 +1,31 @@ import {GrantType} from '@kinde-oss/kinde-typescript-sdk'; import {version} from '../utils/version'; +/** + * @type {import('../../types').KindeState} + */ const initialState = { - user: null, - isLoading: true, - checkSession: null, accessToken: null, - getClaim: null, - getFlag: null, - getToken: null, - getBooleanFlag: null, - getStringFlag: null, - getIntegerFlag: null, - getPermission: null, - getPermissions: null, - permissions: null, + idToken: null, + isAuthenticated: false, + isLoading: true, organization: null, - userOrganizations: null + permissions: [], + user: null, + userOrganiaztions: [], + getAccessToken: () => null, + getBooleanFlag: () => null, + getClaim: () => null, + getFlag: () => null, + getIdToken: () => null, + getIntegerFlag: () => null, + getOrganization: () => null, + getPermission: () => null, + getPermissions: () => [], + getStringFlag: () => null, + getToken: () => null, + getUser: () => null, + getUserOrganizations: () => null }; const SESSION_PREFIX = 'pkce-verifier'; diff --git a/src/frontend/AuthProvider.jsx b/src/frontend/AuthProvider.jsx index db1d9a7..4691773 100644 --- a/src/frontend/AuthProvider.jsx +++ b/src/frontend/AuthProvider.jsx @@ -7,199 +7,48 @@ import React, { } from 'react'; import {config} from '../config/index'; + +/** @type {Record} */ export const flagDataTypeMap = { s: 'string', i: 'integer', b: 'boolean' }; -const handleError = () => { - throw new Error( - 'Oops! Seems like you forgot to wrap your app in .' - ); -}; -/** - * @typedef {Object} AccessToken - * @property {[string]} aud - * @property {number} azp - * @property {number} iat - * @property {string} iss - * @property {string} jti - * @property {string} org_code - * @property {[string]} permissions - * @property {[string]} scp - * @property {string} sub - */ - -/** - * @typedef {Object} IdToken - * @property {string} at_hash - * @property {[string]} aud - * @property {number} auth_time - * @property {string} azp - * @property {string} email - * @property {number} exp - * @property {string} family_name - * @property {string} given_name - * @property {number} iat - * @property {string} iss - * @property {string} jti - * @property {string} name - * @property {[string]} org_codes - * @property {string} sub - * @property {number} updated_at - */ - -/** - * @typedef {Object} KindeUser - * @property {string | null} family_name - User's family name - * @property {string | null} given_name - User's given name - * @property {string | null} picture - URL to user's picture - * @property {string | null} email - User's email - * @property {string | null} id - User's Kinde ID - */ - -/** - * @callback getClaim - * @param {string} claim - Property in a token object - * @param {"access_token" | "id_token"} [tokenKey] - Determines which token to get the claim from - * @returns {{name: string, value: string} | null} - */ - -/** - * @callback getFlag - * @param {string} code - The flag's code on Kinde - * @param {string | number | boolean} defaultValue - Default value if the flag cannot be found - * @param {"b" | "i" | "s"} flagType - The flag's type - * @returns {{code: string, type: string, value: string | number | boolean, is_default: boolean}} - */ - -/** - * @callback getBooleanFlag - * @param {string} code - The flag's code on Kinde - * @param {boolean} defaultValue - Fallback boolean value if the flag cannot be found - * @returns {boolean} - */ - -/** - * @callback getIntegerFlag - * @param {string} code - The flag's code on Kinde - * @param {number} defaultValue - Fallback integer value if the flag cannot be found - * @returns {number} - */ - -/** - * @callback getStringFlag - * @param {string} code - The flag's code on Kinde - * @param {string} defaultValue - Fallback string value if the flag cannot be found - * @returns {string} - */ - -/** - * @callback getPermission - * @param {string} key - The permission's key on Kinde - * @return {{isGranted: boolean, orgCode: string}} - */ - -/** - * @callback getAccessToken - * @return {AccessToken} - */ - -/** - * @callback getToken - * @return {string | null} - */ - -/** - * @callback getIdToken - * @return {IdToken} - */ - -/** - * @callback getPermissions - * @return {[string] | null} - */ - -/** - * @callback getOrganization - * @return {string | null} - */ - -/** - * @callback getUserOrganzations - * @return {[string] | null} - */ - -/** - * @typedef {Object} State - * @property {AccessToken | null} accessToken - Kinde access token - * @property {IdToken | null} idToken - Kinde id token - * @property {string | null} [error] - * @property {boolean | null} isAuthenticated - * @property {boolean | null} isLoading - * @property {string | null} organization - The organization that the current user is logged in to - * @property {[string] | null} permissions - The current user's permissions - * @property {KindeUser | null} user - Kinde user - * @property {[string] | null} userOrganizations - Organizations that the current user belongs to - * @property {getBooleanFlag} getBooleanFlag - * @property {getClaim} getClaim - * @property {getFlag} getFlag - * @property {getIntegerFlag} getIntegerFlag - * @property {getPermission} getPermission - * @property {getStringFlag} getStringFlag - * @property {getAccessToken} getAccessToken - * @property {getToken} getToken - * @property {getPermissions} getPermissions - * @property {getOrganization} getOrganization - * @property {getUserOrganzations} getUserOrganzations - * @property {getIdToken} getIdToken - */ - -/** - * @typedef {Object} KindeFlag - * @property {string} code - * @property {string} type - * @property {string | boolean | number} value - * @property {boolean} is_default - */ - -/** - * @returns {React.Context} - */ const AuthContext = createContext({ - ...config.initialState, - user: handleError, - isLoading: handleError, - getToken: handleError + ...config.initialState }); /** * - * @returns {State} + * @returns {import('../../types').KindeState} */ export const useKindeAuth = () => useContext(AuthContext); +/** + * + * @param {string} url + * @returns {Promise} + */ const tokenFetcher = async (url) => { let response; try { response = await fetch(url); } catch { - throw new RequestError(0); + throw new Error('Failed to fetch token'); } if (response.ok) { const json = await response.json(); return json; } else if (response.status === 401) { - return; + throw new Error('Failed to fetch token'); } }; /** * * @param {{children: React.ReactNode}} props - * @returns {React.Provider>} */ export const KindeProvider = ({children}) => { const [state, setState] = useState({ @@ -213,30 +62,46 @@ export const KindeProvider = ({children}) => { try { const tokens = await tokenFetcher(setupUrl); + if (tokens == undefined) return; + const { - user, - permissions, - organization, - userOrganizations, - featureFlags, accessToken, accessTokenEncoded, - idToken + featureFlags, + idToken, + organization, + permissions, + user, + userOrganizations } = tokens; - const getToken = () => accessTokenEncoded; const getAccessToken = () => accessToken; + const getToken = () => accessTokenEncoded; const getIdToken = () => idToken; const getPermissions = () => permissions; const getOrganization = () => organization; - const getUserOrganzations = () => userOrganizations; - + const getUser = () => user; + const getUserOrganizations = () => userOrganizations; + + /** + * + * @param {string} claim + * @param {"access_token" | "id_token"} tokenKey + */ const getClaim = (claim, tokenKey = 'access_token') => { const token = - tokenKey === 'access_token' ? tokens.access_token : tokens.id_token; + tokenKey === 'access_token' ? tokens.accessToken : tokens.idToken; + // @ts-ignore return token ? {name: claim, value: token[claim]} : null; }; + /** + * + * @param {string} code + * @param {number | string | boolean} defaultValue + * @param {import('../../types').KindeFlagTypeCode} flagType + * @returns {import('../../types').KindeFlag} + */ const getFlag = (code, defaultValue, flagType) => { const flags = featureFlags; const flag = flags && flags[code] ? flags[code] : {}; @@ -247,21 +112,34 @@ export const KindeProvider = ({children}) => { ); } + // @ts-ignore if (flagType && flag.t && flagType !== flag.t) { throw Error( `Flag ${code} is of type ${ + // @ts-ignore flagDataTypeMap[flag.t] } - requested type ${flagDataTypeMap[flagType]}` ); } return { + // @ts-ignore code, + // @ts-ignore type: flagDataTypeMap[flag.t || flagType], + // @ts-ignore value: flag.v == null ? defaultValue : flag.v, - is_default: flag.v == null + // @ts-ignore + is_default: flag.v == null, + defaultValue: defaultValue }; }; + /** + * + * @param {string} code + * @param {boolean} defaultValue + * @returns {boolean | undefined | null} + */ const getBooleanFlag = (code, defaultValue) => { try { const flag = getFlag(code, defaultValue, 'b'); @@ -271,6 +149,12 @@ export const KindeProvider = ({children}) => { } }; + /** + * + * @param {string} code + * @param {string} defaultValue + * @returns {string | undefined | null} + */ const getStringFlag = (code, defaultValue) => { try { const flag = getFlag(code, defaultValue, 's'); @@ -280,6 +164,12 @@ export const KindeProvider = ({children}) => { } }; + /** + * + * @param {string} code + * @param {number} defaultValue + * @returns {number | undefined | null} + */ const getIntegerFlag = (code, defaultValue) => { try { const flag = getFlag(code, defaultValue, 'i'); @@ -289,37 +179,44 @@ export const KindeProvider = ({children}) => { } }; + /** + * + * @param {string} key + * @returns {import('../../types').KindePermission} + */ const getPermission = (key) => { return { - isGranted: permissions.some((p) => p === key), - orgCode: organization + isGranted: permissions.permissions.some((p) => p === key), + orgCode: organization.orgCode }; }; setState((previous) => ({ ...previous, - user, accessToken, idToken, - permissions, + isLoading: false, organization, + permissions, + user, userOrganizations, getAccessToken, - getToken, + getBooleanFlag, getClaim, getFlag, getIdToken, - getBooleanFlag, - getStringFlag, getIntegerFlag, getOrganization, getPermission, getPermissions, - getUserOrganzations, - error: undefined + getStringFlag, + getToken, + getUser, + getUserOrganizations })); } catch (error) { - setState((previous) => ({...previous, isLoading: false, error})); + // @ts-ignore + setState((previous) => ({...previous, isLoading: false, error: error})); } }, [setupUrl]); @@ -353,7 +250,8 @@ export const KindeProvider = ({children}) => { getOrganization, getPermission, getPermissions, - getUserOrganzations, + getUser, + getUserOrganizations, permissions, organization, userOrganizations, @@ -379,8 +277,8 @@ export const KindeProvider = ({children}) => { getOrganization, getPermission, getPermissions, - getUserOrganzations, - getPermission, + getUser, + getUserOrganizations, permissions, organization, userOrganizations, diff --git a/src/handlers/setup.js b/src/handlers/setup.js index 454d5c6..8234174 100644 --- a/src/handlers/setup.js +++ b/src/handlers/setup.js @@ -39,7 +39,10 @@ export const setup = async (routerClient) => { accessTokenEncoded, idToken, user, - permissions, + permissions: { + permissions, + orgCode: organization + }, organization, featureFlags, userOrganizations diff --git a/src/utils/version.js b/src/utils/version.js index c993876..79ab516 100644 --- a/src/utils/version.js +++ b/src/utils/version.js @@ -1,2 +1,2 @@ // Generated by genversion. -export const version = '2.0.0-alpha.2' +export const version = '2.0.0' diff --git a/types.d.ts b/types.d.ts index 65ef122..de407a5 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,3 +1,14 @@ +import { + ClaimTokenType, + CreateOrgURLOptions, + FlagType, + GetFlagType, + LoginURLOptions, + RegisterURLOptions, + SessionManager, + UserType +} from '@kinde-oss/kinde-typescript-sdk'; + export type KindeAccessToken = { aud: string[]; azp: number; @@ -5,7 +16,7 @@ export type KindeAccessToken = { iss: string; jti: string; org_code: string; - permissions: string[]; + permissions: KindePermissions; scp: string[]; sub: string; }; @@ -46,6 +57,11 @@ export type KindePermission = { orgCode: string | null; }; +export type KindeFlagRaw = { + t: KindeFlagTypeCode; + v: string | number | boolean; +}; + export type KindeFlagTypeCode = 'b' | 'i' | 's'; export type KindeFlagTypeValue = 'boolean' | 'integer' | 'string'; @@ -66,23 +82,131 @@ export type KindeOrganizations = { orgCodes: string[]; }; -export type State = { - user: KindeUser; - isLoading: boolean; - isAuthenticated: boolean; - error?: string | undefined; - getToken: () => string | undefined; - getClaim: (claim: string, tokenKey?: string) => any; +export type KindeClient = { + handleRedirectToApp: ( + sessionManager: SessionManager, + callbackURL: URL + ) => Promise; + isAuthenticated: (sessionManager: SessionManager) => Promise; + getUserProfile: (sessionManager: SessionManager) => Promise; + createOrg: ( + sessionManager: SessionManager, + options?: CreateOrgURLOptions + ) => Promise; + getToken: (sessionManager: SessionManager) => Promise; + register: ( + sessionManager: SessionManager, + options?: RegisterURLOptions + ) => Promise; + getUser: (sessionManager: SessionManager) => Promise; + logout: (sessionManager: SessionManager) => Promise; + login: ( + sessionManager: SessionManager, + options?: LoginURLOptions + ) => Promise; + getUserOrganizations: ( + sessionManager: SessionManager + ) => Promise; + getOrganization: ( + sessionManager: SessionManager + ) => Promise; + getBooleanFlag: ( + sessionManager: SessionManager, + code: string, + defaultValue?: boolean | undefined + ) => Promise; + getIntegerFlag: ( + sessionManager: SessionManager, + code: string, + defaultValue?: number | undefined + ) => Promise; + getPermissions: (sessionManager: SessionManager) => Promise<{ + permissions: string[]; + orgCode: string | null; + }>; + getPermission: ( + sessionManager: SessionManager, + name: string + ) => Promise<{ + orgCode: string | null; + isGranted: boolean; + }>; + getClaimValue: ( + sessionManager: SessionManager, + claim: string, + type?: ClaimTokenType + ) => Promise; + getStringFlag: ( + sessionManager: SessionManager, + code: string, + defaultValue?: string | undefined + ) => Promise; + getClaim: ( + sessionManager: SessionManager, + claim: string, + type?: ClaimTokenType + ) => Promise<{ + name: string; + value: unknown; + }>; getFlag: ( + sessionManager: SessionManager, code: string, - defaultValue?: string | boolean | number, - flagType?: KindeFlagTypeCode - ) => KindeFlag; - getBooleanFlag: (code: string, defaultValue: boolean) => boolean; - getStringFlag: (code: string, defaultValue: string) => string; - getIntegerFlag: (code: string, defaultValue: number) => number; - getPermissions: () => KindePermissions; - getPermission: (key: string) => KindePermission; + defaultValue?: string | number | boolean | undefined, + type?: keyof FlagType | undefined + ) => Promise; +}; + +export type KindeState = { + accessToken: KindeAccessToken | null; + error?: string | null; + idToken: KindeIdToken | null; + isAuthenticated: boolean | null; + isLoading: boolean | null; + organization: KindeOrganization; + permissions: KindePermissions; + user: KindeUser | null; + userOrganizations: KindeOrganizations; + getAccessToken: () => KindeAccessToken | null; + getBooleanFlag: ( + code: string, + defaultValue: boolean + ) => boolean | null | undefined; + getClaim: ( + claim: string, + tokenKey?: 'access_token' | 'id_token' + ) => {name: string; value: string} | null; + getFlag: ( + code: string, + defaultValue: string | number | boolean, + flagType: KindeFlagTypeCode + ) => KindeFlag | null; + getIdToken: () => KindeIdToken | null; + getIntegerFlag: ( + code: string, + defaultValue: number + ) => number | null | undefined; getOrganization: () => KindeOrganization; - getUserOrganizations: () => KindeOrganizations; + getPermission: ( + key: string + ) => {isGranted: boolean; orgCode: string | null} | null; + getPermissions: () => KindePermissions; + getStringFlag: ( + code: string, + defaultValue: string + ) => string | null | undefined; + getToken: () => string | null; + getUser: () => KindeUser | null; + getUserOrganizations: () => KindeOrganizations | null; +}; + +export type KindeSetupResponse = { + accessToken: KindeAccessToken; + accessTokenEncoded: string; + idToken: KindeIdToken; + user: KindeUser; + permissions: KindePermissions; + organization: KindeOrganization; + featureFlags: Record; + userOrganizations: KindeOrganizations; };