Skip to content

Commit

Permalink
Merge pull request #407 from MadAppGang/oidc_provider_data
Browse files Browse the repository at this point in the history
receiving data from the provider
  • Loading branch information
BeautifuLie authored Jun 19, 2023
2 parents d2e154d + 1f7b7ca commit 9ea9a93
Show file tree
Hide file tree
Showing 15 changed files with 164 additions and 31 deletions.
34 changes: 21 additions & 13 deletions web/api/federated_oidc_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ func (ar *Router) OIDCLoginComplete(w http.ResponseWriter, r *http.Request) {
return
}

claims, fsess, err := ar.completeOIDCAuth(r, app)
claims, fsess, providerData, err := ar.completeOIDCAuth(r, app)
if err != nil {
ar.ErrorResponse(w, err)
return
Expand Down Expand Up @@ -229,6 +229,7 @@ func (ar *Router) OIDCLoginComplete(w http.ResponseWriter, r *http.Request) {

authResult.CallbackUrl = fsess.CallbackUrl
authResult.Scopes = requestedScopes
authResult.ProviderData = *providerData

ar.ServeJSON(w, locale, http.StatusOK, authResult)
}
Expand Down Expand Up @@ -281,7 +282,7 @@ func extractField(data map[string]any, key string) string {
return ""
}

func (ar *Router) completeOIDCAuth(r *http.Request, app model.AppData) (map[string]interface{}, *model.FederatedSession, error) {
func (ar *Router) completeOIDCAuth(r *http.Request, app model.AppData) (map[string]interface{}, *model.FederatedSession, *providerData, error) {
ctx := r.Context()

var fsess *model.FederatedSession
Expand All @@ -290,33 +291,33 @@ func (ar *Router) completeOIDCAuth(r *http.Request, app model.AppData) (map[stri

oidcProvider, verifier, err := getCachedOIDCProvider(ctx, app)
if err != nil {
return nil, fsess, NewLocalizedError(http.StatusInternalServerError, locale, l.ErrorFederatedOidcProviderError, err)
return nil, fsess, nil, NewLocalizedError(http.StatusInternalServerError, locale, l.ErrorFederatedOidcProviderError, err)
}

authCode := r.URL.Query().Get("code")
if len(authCode) == 0 {
log.Println("failed ot authorize user with OIDC: no code in response", r.URL.Query())
return nil, fsess, NewLocalizedError(http.StatusBadRequest, locale, l.ErrorFederatedCodeError)
return nil, fsess, nil, NewLocalizedError(http.StatusBadRequest, locale, l.ErrorFederatedCodeError)
}

sn := oidcSessionKey(app.ID, app.OIDCSettings.ProviderName)
value, err := ar.getFromSession(SessionNameOIDC, sn, r)
if err != nil {
return nil, fsess, NewLocalizedError(http.StatusBadRequest, locale, l.ErrorFederatedUnmarshalSessionError, err)
return nil, fsess, nil, NewLocalizedError(http.StatusBadRequest, locale, l.ErrorFederatedUnmarshalSessionError, err)
}

fsess, err = model.UnmarshalFederatedSession(value)
if err != nil {
return nil, fsess, NewLocalizedError(http.StatusBadRequest, locale, l.ErrorFederatedUnmarshalSessionError, err)
return nil, fsess, nil, NewLocalizedError(http.StatusBadRequest, locale, l.ErrorFederatedUnmarshalSessionError, err)
}

if fsess.AppId != app.ID {
return nil, fsess, NewLocalizedError(http.StatusBadRequest, locale, l.ErrorFederatedSessionAPPIDMismatch, fsess.AppId, app.ID)
return nil, fsess, nil, NewLocalizedError(http.StatusBadRequest, locale, l.ErrorFederatedSessionAPPIDMismatch, fsess.AppId, app.ID)
}

errv := validateState(r, fsess.AuthUrl)
if errv != nil {
return nil, fsess, NewLocalizedError(http.StatusBadRequest, locale, l.ErrorFederatedStateError)
return nil, fsess, nil, NewLocalizedError(http.StatusBadRequest, locale, l.ErrorFederatedStateError)
}

// Configure an OpenID Connect aware OAuth2 client.
Expand All @@ -330,28 +331,35 @@ func (ar *Router) completeOIDCAuth(r *http.Request, app model.AppData) (map[stri

oauth2Token, err := oauth2Config.Exchange(ctx, authCode)
if err != nil {
return nil, fsess, NewLocalizedError(http.StatusBadRequest, locale, l.ErrorFederatedExchangeError, err)
return nil, fsess, nil, NewLocalizedError(http.StatusBadRequest, locale, l.ErrorFederatedExchangeError, err)
}

// Extract the ID Token from OAuth2 token.
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
if !ok {
return nil, fsess, NewLocalizedError(http.StatusBadRequest, locale, l.ErrorFederatedIDtokenMissing)
return nil, fsess, nil, NewLocalizedError(http.StatusBadRequest, locale, l.ErrorFederatedIDtokenMissing)
}

// Parse and verify ID Token payload.
idToken, err := verifier.Verify(ctx, rawIDToken)
if err != nil {
return nil, fsess, NewLocalizedError(http.StatusBadRequest, locale, l.ErrorFederatedIDtokenInvalid, err)
return nil, fsess, nil, NewLocalizedError(http.StatusBadRequest, locale, l.ErrorFederatedIDtokenInvalid, err)
}

// Extract custom claims
var claims map[string]interface{}
if err := idToken.Claims(&claims); err != nil {
return nil, fsess, NewLocalizedError(http.StatusBadRequest, locale, l.ErrorFederatedClaimsError, err)
return nil, fsess, nil, NewLocalizedError(http.StatusBadRequest, locale, l.ErrorFederatedClaimsError, err)
}

return claims, fsess, nil
providerData := &providerData{
AccessToken: oauth2Token.AccessToken,
RefreshToken: oauth2Token.RefreshToken,
TokenType: oauth2Token.TokenType,
Expiry: oauth2Token.Expiry,
}

return claims, fsess, providerData, nil
}

func (ar *Router) tryFindFederatedUser(provider, fedUserID, email string) (model.User, error) {
Expand Down
22 changes: 15 additions & 7 deletions web/api/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,21 @@ const (

// AuthResponse is a response with successful auth data.
type AuthResponse struct {
AccessToken string `json:"access_token,omitempty" bson:"access_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty" bson:"refresh_token,omitempty"`
User model.User `json:"user,omitempty" bson:"user,omitempty"`
Require2FA bool `json:"require_2fa" bson:"require_2fa"`
Enabled2FA bool `json:"enabled_2fa" bson:"enabled_2fa"`
CallbackUrl string `json:"callback_url,omitempty" bson:"callback_url,omitempty"`
Scopes []string `json:"scopes,omitempty" bson:"scopes,omitempty"`
AccessToken string `json:"access_token,omitempty" bson:"access_token,omitempty"`
RefreshToken string `json:"refresh_token,omitempty" bson:"refresh_token,omitempty"`
User model.User `json:"user,omitempty" bson:"user,omitempty"`
Require2FA bool `json:"require_2fa" bson:"require_2fa"`
Enabled2FA bool `json:"enabled_2fa" bson:"enabled_2fa"`
CallbackUrl string `json:"callback_url,omitempty" bson:"callback_url,omitempty"`
Scopes []string `json:"scopes,omitempty" bson:"scopes,omitempty"`
ProviderData providerData `json:"provider_data,omitempty" bson:"provider_data,omitempty"`
}

type providerData struct {
AccessToken string `json:"access_token,omitempty"`
TokenType string `json:"token_type,omitempty"`
RefreshToken string `json:"refresh_token,omitempty"`
Expiry time.Time `json:"expiry,omitempty"`
}

type login struct {
Expand Down
19 changes: 18 additions & 1 deletion web_apps_src/identifo.js/dist/identifo.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ interface TokenManager {
saveToken: (token: string, tokenType: TokenType) => boolean;
getToken: (tokenType: TokenType) => string;
deleteToken: (tokenType: TokenType) => void;
saveOIDCProviderData: (data: Record<string, unknown>) => void;
getOIDCProviderData: () => Record<string, string>;
}
declare type IdentifoConfig = {
issuer?: string;
Expand Down Expand Up @@ -76,6 +78,8 @@ declare class TokenService {
validateToken(token: string, audience: string, issuer?: string): Promise<boolean>;
parseJWT(token: string): JWTPayload;
isJWTExpired(token: JWTPayload): boolean;
saveOIDCProviderData(data: Record<string, unknown>): void;
getOIDCProviderData(): Record<string, string>;
saveToken(token: string, type?: TokenType): boolean;
removeToken(type?: TokenType): void;
getToken(type?: TokenType): ClientToken | null;
Expand Down Expand Up @@ -137,6 +141,7 @@ interface LoginResponse {
};
scopes?: string[];
callbackUrl?: string;
provider_data?: OIDCProviderData;
}
interface EnableTFAResponse {
provisioning_uri?: string;
Expand All @@ -147,6 +152,13 @@ interface TokenResponse {
access_token?: string;
refresh_token?: string;
}
interface OIDCProviderData {
token_type?: string;
access_token?: string;
refresh_token?: string;
expiry?: string;
[x: string]: string | undefined;
}
interface AppSettingsResponse {
anonymousResitrationAllowed: boolean;
active: boolean;
Expand Down Expand Up @@ -239,6 +251,7 @@ declare class API {
scopes: string[];
}): Promise<LoginResponse>;
invite(email: string, role: string, callbackUrl: string): Promise<InviteResponse>;
handleOIDCResponse<T extends LoginResponse>(response: T): T;
storeToken<T extends TokenResponse>(response: T): T;
}

Expand All @@ -259,6 +272,7 @@ declare class IdentifoAuth {
handleAuthentication(): Promise<boolean>;
private getTokenFromUrl;
getToken(): Promise<ClientToken | null>;
getOIDCProviderData(): Record<string, string>;
renewSession(): Promise<string>;
private renewSessionWithToken;
}
Expand All @@ -275,11 +289,14 @@ declare class StorageManager implements TokenManager {
storageType: 'localStorage' | 'sessionStorage';
access: string;
refresh: string;
oidcProviderDataKey: string;
isAccessible: boolean;
constructor(storageType: 'localStorage' | 'sessionStorage', accessKey?: string, refreshKey?: string);
saveToken(token: string, tokenType: TokenType): boolean;
getToken(tokenType: TokenType): string;
deleteToken(tokenType: TokenType): void;
getOIDCProviderData(): Record<string, string>;
saveOIDCProviderData(data?: Record<string, unknown>): void;
}

declare class LocalStorage extends StorageManager {
Expand Down Expand Up @@ -501,4 +518,4 @@ declare class CDK {
private getLoginTypes;
}

export { APIErrorCodes, ApiError, ApiRequestError, AppSettingsResponse, CDK, ClientToken, CookieStorage as CookieStorageManager, EnableTFAResponse, FederatedLoginProvider, IdentifoAuth, IdentifoConfig, InviteResponse, JWTPayload, LocalStorage as LocalStorageManager, LoginResponse, LoginTypes, Routes, ServerSettingsLoginTypes, SessionStorage as SessionStorageManager, State, StateCallback, StateError, StateLoading, StateLogin, StateLoginOidc, StateLoginPhone, StateLoginPhoneVerify, StateLogout, StatePasswordForgot, StatePasswordForgotSuccess, StatePasswordForgotTFASelect, StatePasswordForgotTFAVerify, StatePasswordReset, StateRegister, StateTFASetupApp, StateTFASetupEmail, StateTFASetupSMS, StateTFASetupSelect, StateTFAVerifyApp, StateTFAVerifyEmailSms, StateTFAVerifySelect, StateWithError, States, SuccessResponse, TFALoginVerifyRoutes, TFARequiredRespopnse, TFAResetVerifyRoutes, TFASetupRoutes, TFAStatus, TFAType, TokenManager, TokenResponse, TokenType, UpdateUser, UrlBuilderInit, UrlFlows, User, typeToPasswordForgotTFAVerifyRoute, typeToSetupRoute, typeToTFAVerifyRoute };
export { APIErrorCodes, ApiError, ApiRequestError, AppSettingsResponse, CDK, ClientToken, CookieStorage as CookieStorageManager, EnableTFAResponse, FederatedLoginProvider, IdentifoAuth, IdentifoConfig, InviteResponse, JWTPayload, LocalStorage as LocalStorageManager, LoginResponse, LoginTypes, OIDCProviderData, Routes, ServerSettingsLoginTypes, SessionStorage as SessionStorageManager, State, StateCallback, StateError, StateLoading, StateLogin, StateLoginOidc, StateLoginPhone, StateLoginPhoneVerify, StateLogout, StatePasswordForgot, StatePasswordForgotSuccess, StatePasswordForgotTFASelect, StatePasswordForgotTFAVerify, StatePasswordReset, StateRegister, StateTFASetupApp, StateTFASetupEmail, StateTFASetupSMS, StateTFASetupSelect, StateTFAVerifyApp, StateTFAVerifyEmailSms, StateTFAVerifySelect, StateWithError, States, SuccessResponse, TFALoginVerifyRoutes, TFARequiredRespopnse, TFAResetVerifyRoutes, TFASetupRoutes, TFAStatus, TFAType, TokenManager, TokenResponse, TokenType, UpdateUser, UrlBuilderInit, UrlFlows, User, typeToPasswordForgotTFAVerifyRoute, typeToSetupRoute, typeToTFAVerifyRoute };
30 changes: 29 additions & 1 deletion web_apps_src/identifo.js/dist/identifo.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion web_apps_src/identifo.js/dist/identifo.js.map

Large diffs are not rendered by default.

30 changes: 29 additions & 1 deletion web_apps_src/identifo.js/dist/identifo.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ class API {
oidcVerify(data) {
return __async$3(this, null, function* () {
const url = `/auth/federated/oidc/complete?appId=${this.appId}&state=${data.state}&code=${data.code}`;
return this.post(url, { scopes: data.scopes }, { credentials: "include" }).then((r) => this.storeToken(r));
return this.post(url, { scopes: data.scopes }, { credentials: "include" }).then((r) => this.storeToken(r)).then((r) => this.handleOIDCResponse(r));
});
}
invite(email, role, callbackUrl) {
Expand All @@ -331,6 +331,12 @@ class API {
});
});
}
handleOIDCResponse(response) {
if (response.provider_data) {
this.tokenService.saveOIDCProviderData(response.provider_data);
}
return response;
}
storeToken(response) {
if (response.access_token) {
this.tokenService.saveToken(response.access_token, "access");
Expand Down Expand Up @@ -367,6 +373,7 @@ class StorageManager {
this.storageType = "localStorage";
this.access = `${this.preffix}access_token`;
this.refresh = `${this.preffix}refresh_token`;
this.oidcProviderDataKey = `${this.preffix}oidc_provider_data`;
this.isAccessible = true;
this.access = accessKey ? this.preffix + accessKey : this.access;
this.refresh = refreshKey ? this.preffix + refreshKey : this.refresh;
Expand All @@ -386,6 +393,18 @@ class StorageManager {
deleteToken(tokenType) {
window[this.storageType].removeItem(this[tokenType]);
}
getOIDCProviderData() {
var _a;
try {
return JSON.parse((_a = window[this.storageType].getItem(this.oidcProviderDataKey)) != null ? _a : "{}");
} catch (error) {
console.error(error);
return {};
}
}
saveOIDCProviderData(data = {}) {
window[this.storageType].setItem(this.oidcProviderDataKey, JSON.stringify(data));
}
}

class LocalStorage extends StorageManager {
Expand Down Expand Up @@ -467,6 +486,12 @@ class TokenService {
}
return false;
}
saveOIDCProviderData(data) {
this.tokenManager.saveOIDCProviderData(data);
}
getOIDCProviderData() {
return this.tokenManager.getOIDCProviderData();
}
saveToken(token, type = "access") {
if (type === "access") {
this.isAuth = true;
Expand Down Expand Up @@ -661,6 +686,9 @@ class IdentifoAuth {
return Promise.resolve(null);
});
}
getOIDCProviderData() {
return this.tokenService.getOIDCProviderData();
}
renewSession() {
return __async$1(this, null, function* () {
try {
Expand Down
2 changes: 1 addition & 1 deletion web_apps_src/identifo.js/dist/identifo.mjs.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions web_apps_src/identifo.js/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion web_apps_src/identifo.js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@identifo/identifo-auth-js",
"version": "3.3.9",
"version": "3.4.6",
"description": "Library for web-auth through Identifo",
"main": "./dist/identifo.js",
"module": "./dist/identifo.mjs",
Expand Down
4 changes: 4 additions & 0 deletions web_apps_src/identifo.js/src/IdentifoAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ class IdentifoAuth {
return Promise.resolve(null);
}

getOIDCProviderData(): Record<string, string> {
return this.tokenService.getOIDCProviderData();
}

async renewSession(): Promise<string> {
try {
const { access, refresh } = await this.renewSessionWithToken();
Expand Down
Loading

0 comments on commit 9ea9a93

Please sign in to comment.