diff --git a/packages/connect-react/src/components/login/LoginErrorScreenHard.tsx b/packages/connect-react/src/components/login/LoginErrorScreenHard.tsx index 7d9e0d8be..30227ed1c 100644 --- a/packages/connect-react/src/components/login/LoginErrorScreenHard.tsx +++ b/packages/connect-react/src/components/login/LoginErrorScreenHard.tsx @@ -61,7 +61,11 @@ const LoginErrorScreenHard = ({ previousAssertionOptions }: Props) => { setLoading(false); try { - await config.onComplete(connectLoginFinishToComplete(resFinish.val), getConnectService().encodeClientState()); + await config.onComplete( + connectLoginFinishToComplete(resFinish.val), + getConnectService().encodeClientState(), + resFinish.val.passkeyOperation.identifierValue, + ); } catch { return handleSituation(LoginSituationCode.CtApiNotAvailablePostAuthenticator); } diff --git a/packages/connect-react/src/components/login/LoginErrorScreenSoft.tsx b/packages/connect-react/src/components/login/LoginErrorScreenSoft.tsx index 5c6b5d506..f0d67d660 100644 --- a/packages/connect-react/src/components/login/LoginErrorScreenSoft.tsx +++ b/packages/connect-react/src/components/login/LoginErrorScreenSoft.tsx @@ -56,7 +56,11 @@ const LoginErrorScreenSoft = ({ previousAssertionOptions }: Props) => { } try { - await config.onComplete(connectLoginFinishToComplete(resFinish.val), getConnectService().encodeClientState()); + await config.onComplete( + connectLoginFinishToComplete(resFinish.val), + getConnectService().encodeClientState(), + resFinish.val.passkeyOperation.identifierValue, + ); setLoading(false); } catch { handleSituation(LoginSituationCode.CtApiNotAvailablePostAuthenticator); diff --git a/packages/connect-react/src/components/login/LoginHybridScreen.tsx b/packages/connect-react/src/components/login/LoginHybridScreen.tsx index f8b56084e..2f59dcbdc 100644 --- a/packages/connect-react/src/components/login/LoginHybridScreen.tsx +++ b/packages/connect-react/src/components/login/LoginHybridScreen.tsx @@ -32,7 +32,11 @@ const LoginHybridScreen = (resStart: ConnectLoginStartRsp) => { } try { - await config.onComplete(connectLoginFinishToComplete(res.val), getConnectService().encodeClientState()); + await config.onComplete( + connectLoginFinishToComplete(res.val), + getConnectService().encodeClientState(), + res.val.passkeyOperation.identifierValue, + ); } catch { return handleSituation(LoginSituationCode.CtApiNotAvailablePostAuthenticator); } diff --git a/packages/connect-react/src/components/login/LoginInitScreen.tsx b/packages/connect-react/src/components/login/LoginInitScreen.tsx index 64f610192..9a98bc158 100644 --- a/packages/connect-react/src/components/login/LoginInitScreen.tsx +++ b/packages/connect-react/src/components/login/LoginInitScreen.tsx @@ -178,7 +178,11 @@ const LoginInitScreen: FC = ({ showFallback = false }) => { } try { - await config.onComplete(connectLoginFinishToComplete(res.val), getConnectService().encodeClientState()); + await config.onComplete( + connectLoginFinishToComplete(res.val), + getConnectService().encodeClientState(), + res.val.passkeyOperation.identifierValue, + ); } catch { return handleSituation(LoginSituationCode.CtApiNotAvailablePostAuthenticator); } @@ -228,7 +232,11 @@ const LoginInitScreen: FC = ({ showFallback = false }) => { } try { - await config.onComplete(connectLoginFinishToComplete(res.val), getConnectService().encodeClientState()); + await config.onComplete( + connectLoginFinishToComplete(res.val), + getConnectService().encodeClientState(), + res.val.passkeyOperation.identifierValue, + ); } catch { void getConnectService().recordEventLoginErrorUntyped(); return handleSituation(LoginSituationCode.CtApiNotAvailablePostAuthenticator); diff --git a/packages/connect-react/src/components/login/LoginPasskeyReLoginScreen.tsx b/packages/connect-react/src/components/login/LoginPasskeyReLoginScreen.tsx index ad60a610d..ec628896c 100644 --- a/packages/connect-react/src/components/login/LoginPasskeyReLoginScreen.tsx +++ b/packages/connect-react/src/components/login/LoginPasskeyReLoginScreen.tsx @@ -53,7 +53,11 @@ export const LoginPasskeyReLoginScreen = () => { } try { - await config.onComplete(connectLoginFinishToComplete(resFinish.val), getConnectService().encodeClientState()); + await config.onComplete( + connectLoginFinishToComplete(resFinish.val), + getConnectService().encodeClientState(), + resFinish.val.passkeyOperation.identifierValue, + ); } catch { return handleSituation(LoginSituationCode.CtApiNotAvailablePostAuthenticator); } diff --git a/packages/types/src/connect/config.ts b/packages/types/src/connect/config.ts index fca1a6e3f..0d5970293 100644 --- a/packages/types/src/connect/config.ts +++ b/packages/types/src/connect/config.ts @@ -4,7 +4,7 @@ export type CorbadoConnectLoginConfig = { onFallbackCustom?(identifier: string, code: string, payload: string): void; onError?(error: string): void; onLoaded?(message: string, isFallBackTriggered: boolean): void; - onComplete(signedPasskeyData: string, clientState: string): Promise; + onComplete(signedPasskeyData: string, clientState: string, webauthnId: string): Promise; onConditionalLoginStart?(ac: AbortController): void; onLoginStart?(): void; onHelpClick?(): void; diff --git a/packages/web-core/openapi/spec_v2.yaml b/packages/web-core/openapi/spec_v2.yaml index 6f19bd993..f6892816b 100644 --- a/packages/web-core/openapi/spec_v2.yaml +++ b/packages/web-core/openapi/spec_v2.yaml @@ -31,6 +31,8 @@ tags: description: All API calls to manage users - name: CorbadoConnect description: All API calls that are related to a connect process + - name: OIDC + description: All API calls that are related to OpenID Connect paths: /v2/process-config: @@ -933,6 +935,24 @@ paths: "404": description: No process was found for the token + /v2/oidc/userInfo: + get: + summary: OIDC user info + description: Retrieves user information in accordance with the OpenID Connect (OIDC) standard. + operationId: OIDCUserInfoGet + tags: + - OIDC + responses: + "200": + description: User information as per OIDC standard. + content: + application/json: + schema: + $ref: "#/components/schemas/oidcUserInfoRsp" + "401": + description: Unauthorized - Invalid or missing token + + components: ################################################################### # Security schemes # @@ -1392,6 +1412,7 @@ components: required: - session - signedPasskeyData + - passkeyOperation properties: passkeyOperation: $ref: "#/components/schemas/passkeyOperation" @@ -1681,6 +1702,18 @@ components: processId: type: string + oidcUserInfoRsp: + type: object + required: + - sub + properties: + sub: + type: string + email: + type: string + email_verified: + type: boolean + aaguidDetails: type: object required: diff --git a/packages/web-core/src/api/v2/api.ts b/packages/web-core/src/api/v2/api.ts index db0b15674..1fd5b5118 100644 --- a/packages/web-core/src/api/v2/api.ts +++ b/packages/web-core/src/api/v2/api.ts @@ -515,7 +515,7 @@ export interface ConnectLoginFinishRsp { * @type {PasskeyOperation} * @memberof ConnectLoginFinishRsp */ - 'passkeyOperation'?: PasskeyOperation; + 'passkeyOperation': PasskeyOperation; /** * * @type {string} @@ -1902,6 +1902,31 @@ export const NativeMetaDeviceOwnerAuthEnum = { export type NativeMetaDeviceOwnerAuthEnum = typeof NativeMetaDeviceOwnerAuthEnum[keyof typeof NativeMetaDeviceOwnerAuthEnum]; +/** + * + * @export + * @interface OidcUserInfoRsp + */ +export interface OidcUserInfoRsp { + /** + * + * @type {string} + * @memberof OidcUserInfoRsp + */ + 'sub': string; + /** + * + * @type {string} + * @memberof OidcUserInfoRsp + */ + 'email'?: string; + /** + * + * @type {boolean} + * @memberof OidcUserInfoRsp + */ + 'email_verified'?: boolean; +} /** * * @export @@ -5328,6 +5353,112 @@ export class CorbadoConnectApi extends BaseAPI { +/** + * OIDCApi - axios parameter creator + * @export + */ +export const OIDCApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * Retrieves user information in accordance with the OpenID Connect (OIDC) standard. + * @summary OIDC user info + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + oIDCUserInfoGet: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/v2/oidc/userInfo`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication bearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + // authentication projectID required + await setApiKeyToObject(localVarHeaderParameter, "X-Corbado-ProjectID", configuration) + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * OIDCApi - functional programming interface + * @export + */ +export const OIDCApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = OIDCApiAxiosParamCreator(configuration) + return { + /** + * Retrieves user information in accordance with the OpenID Connect (OIDC) standard. + * @summary OIDC user info + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async oIDCUserInfoGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.oIDCUserInfoGet(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + } +}; + +/** + * OIDCApi - factory interface + * @export + */ +export const OIDCApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = OIDCApiFp(configuration) + return { + /** + * Retrieves user information in accordance with the OpenID Connect (OIDC) standard. + * @summary OIDC user info + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + oIDCUserInfoGet(options?: any): AxiosPromise { + return localVarFp.oIDCUserInfoGet(options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * OIDCApi - object-oriented interface + * @export + * @class OIDCApi + * @extends {BaseAPI} + */ +export class OIDCApi extends BaseAPI { + /** + * Retrieves user information in accordance with the OpenID Connect (OIDC) standard. + * @summary OIDC user info + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof OIDCApi + */ + public oIDCUserInfoGet(options?: AxiosRequestConfig) { + return OIDCApiFp(this.configuration).oIDCUserInfoGet(options).then((request) => request(this.axios, this.basePath)); + } +} + + + /** * UsersApi - axios parameter creator * @export diff --git a/playground/connect-next/app/(no-auth)/login/WrappedLogin.tsx b/playground/connect-next/app/(no-auth)/login/WrappedLogin.tsx index 0ae125564..7fa43fb02 100644 --- a/playground/connect-next/app/(no-auth)/login/WrappedLogin.tsx +++ b/playground/connect-next/app/(no-auth)/login/WrappedLogin.tsx @@ -82,7 +82,8 @@ const WrappedLogin = ({ clientState }: Props) => { }} onError={(error: string) => console.log('error', error)} onLoaded={(msg: string) => console.log('component has loaded: ' + msg)} - onComplete={async (signedPasskeyData: string, newClientState: string) => { + onComplete={async (signedPasskeyData: string, newClientState: string, webauthnId: string) => { + console.log('webauthnId', webauthnId); await postPasskeyLoginNew(signedPasskeyData, newClientState); }} onSignupClick={() => router.push('/')}