Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Injiweb 1203] get the authorization server endpoint from its well-known endpoint #229

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
Open
25 changes: 18 additions & 7 deletions inji-web-proxy/proxy_server.js

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this folder from your local now.

Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,36 @@ const PORT = process.env.PORT;
app.use(express.json());
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.urlencoded({extended: true}));

app.all('*', async (req, res) => {
delete req.headers.host
delete req.headers.referer

const API_URL = process.env.MIMOTO_HOST;
const PATH = req.url
let targetHost;
switch (req.headers['target-server']) {
case 'oauth':
targetHost = req.headers['target-host'];
break;
default:
targetHost = process.env['MIMOTO_HOST'];
}
delete req.headers['target-server'];
delete req.headers['target-host'];
const path = req.url;

try {

let response = await axios({
method: req.method,
responseType: PATH.indexOf("/download") === -1 ? "json" : "arraybuffer",
url: `${API_URL + PATH}`,
responseType:
path.indexOf("/download") === -1 ? "json" : "arraybuffer",
url: `${targetHost + path}`,
data: new URLSearchParams(req.body),
headers: req.headers
});

if(PATH.indexOf("/download") === -1){
if (path.indexOf("/download") === -1) {
res.status(response.status).json(response.data);
} else {
res.setHeader('Access-Control-Allow-Origin', '*'); // Change '*' to specific origin if needed
Expand All @@ -43,7 +54,7 @@ app.all('*', async (req, res) => {
if (error.response) {
res.status(error.response.status).json(error.response.data);
} else {
res.status(500).json({ error: error.message });
res.status(500).json({error: error.message});
}
}
});
Expand Down
10 changes: 8 additions & 2 deletions inji-web/src/__tests__/utils/api.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe('Testing API Class', () => {
});

test('Check mimotoHost property', () => {
expect(apiModule.api.mimotoHost).toBe('https://api.collab.mossip.net/v1/mimoto');
expect(apiModule.api.proxyServerHost).toBe('https://api.collab.mossip.net/v1/mimoto');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@PuBHARGAVI we dont have now proxy server. is this still hold true?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed this back to mimotoHost

});

test('Check authorizationRedirectionUrl property', () => {
Expand Down Expand Up @@ -152,7 +152,13 @@ describe('Testing API Class', () => {
codeVerifier: 'verifier123'
};

const url = apiModule.api.authorization(currentIssuer, credentialWellknown, filterCredentialWellknown, state, code_challenge);
const url = apiModule.api.authorization(
currentIssuer,
filterCredentialWellknown,
state,
code_challenge,
'http://auth.server/authorize'
);
expect(url).toBe(
'http://auth.server/authorize' +
'?response_type=code&' +
Expand Down
87 changes: 66 additions & 21 deletions inji-web/src/components/Credentials/CredentialList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, {useState} from "react";
import {Credential} from "./Crendential";
import {useSelector} from "react-redux";
import {RootState} from "../../types/redux";
Expand All @@ -8,35 +8,80 @@ import {RequestStatus} from "../../hooks/useFetch";
import {SpinningLoader} from "../Common/SpinningLoader";
import {CredentialListProps} from "../../types/components";
import {HeaderTile} from "../Common/HeaderTile";
import {DownloadResult} from "../Redirection/DownloadResult";
import {toast} from "react-toastify";

export const CredentialList: React.FC<CredentialListProps> = ({state}) => {

const [errorObj, setErrorObj] = useState({
code: "",
message: ""
});
const [toastError, setToastError] = useState("");
const credentials = useSelector((state: RootState) => state.credentials);
const {t} = useTranslation("CredentialsPage");

if (state === RequestStatus.LOADING) {
return <SpinningLoader />
return <SpinningLoader />;
}

if (toastError !== "") {
toast.error(t("errorContent"), {onClose: () => setToastError("")});
}

if (state === RequestStatus.ERROR ||
!credentials?.filtered_credentials?.credential_configurations_supported ||
(credentials?.filtered_credentials?.credential_configurations_supported &&
credentials?.filtered_credentials?.credential_configurations_supported.length === 0 )
if (
state === RequestStatus.ERROR ||
!credentials?.filtered_credentials
?.credential_configurations_supported ||
(credentials?.filtered_credentials
?.credential_configurations_supported &&
credentials?.filtered_credentials
?.credential_configurations_supported.length === 0)
) {
return <div>
<HeaderTile content={t("containerHeading")} subContent={t("containerSubHeading")}/>
<EmptyListContainer content={t("emptyContainerContent")}/>
return (
<div>
<HeaderTile
content={t("containerHeading")}
subContent={t("containerSubHeading")}
/>
<EmptyListContainer content={t("emptyContainerContent")} />
</div>
);
}

return <React.Fragment>
<HeaderTile content={t("containerHeading")} subContent={t("containerSubHeading")}/>
<div className="flex flex-wrap gap-3 p-4 pb-20 justify-start">
{credentials?.filtered_credentials && Object.keys(credentials?.filtered_credentials?.credential_configurations_supported).map((credentialId: string, index: number) => (
<Credential credentialId={credentialId} credentialWellknown={credentials?.filtered_credentials} key={index} index={index}/>
))}
</div>
</React.Fragment>
}


return (
<React.Fragment>
{errorObj.code === "" ? (
<React.Fragment>
<HeaderTile
content={t("containerHeading")}
subContent={t("containerSubHeading")}
/>
<div className="flex flex-wrap gap-3 p-4 pb-20 justify-start">
{credentials?.filtered_credentials &&
Object.keys(
credentials?.filtered_credentials
?.credential_configurations_supported
).map((credentialId: string, index: number) => (
<Credential
credentialId={credentialId}
credentialWellknown={
credentials?.filtered_credentials
}
key={index}
index={index}
setErrorObj={setErrorObj}
setToastError={setToastError}
/>
))}
</div>
</React.Fragment>
) : (
<DownloadResult
title={t(errorObj.code)}
subTitle={t(errorObj.message)}
state={RequestStatus.ERROR}
/>
)}
</React.Fragment>
);
};
142 changes: 116 additions & 26 deletions inji-web/src/components/Credentials/Crendential.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,137 @@
import React, {useState} from "react";
import React, {useEffect, useState} from "react";
import {getObjectForCurrentLanguage} from "../../utils/i18n";
import {ItemBox} from "../Common/ItemBox";
import {generateCodeChallenge, generateRandomString} from "../../utils/misc";
import {addNewSession} from "../../utils/sessions";
import {useSelector} from "react-redux";
import {api} from "../../utils/api";
import {CredentialProps} from "../../types/components";
import {CodeChallengeObject, CredentialConfigurationObject} from "../../types/data";
import {
AuthServerWellknownObject,
CredentialConfigurationObject
} from "../../types/data";
import {RootState} from "../../types/redux";
import {DataShareExpiryModal} from "../../modals/DataShareExpiryModal";
import {useTranslation} from "react-i18next";
import {RequestStatus, useFetch} from "../../hooks/useFetch";

export const Credential: React.FC<CredentialProps> = (props) => {
const {t} = useTranslation("CredentialsPage");
const {state, response, fetchRequest} = useFetch();
const selectedIssuer = useSelector((state: RootState) => state.issuers);
const [credentialExpiry, setCredentialExpiry] = useState<boolean>(false);
const language = useSelector((state: RootState) => state.common.language);
const filteredCredentialConfig: CredentialConfigurationObject = props.credentialWellknown.credential_configurations_supported[props.credentialId];
const credentialObject = getObjectForCurrentLanguage(filteredCredentialConfig.display, language);
const vcStorageExpiryLimitInTimes = useSelector((state: RootState) => state.common.vcStorageExpiryLimitInTimes);
const filteredCredentialConfig: CredentialConfigurationObject =
props.credentialWellknown.credential_configurations_supported[
props.credentialId
];
const credentialObject = getObjectForCurrentLanguage(
filteredCredentialConfig.display,
language
);
const vcStorageExpiryLimitInTimes = useSelector(
(state: RootState) => state.common.vcStorageExpiryLimitInTimes
);
const [authorizationReqState, setAuthorizationRequestState] = useState("");
const [codeChallenge, setCodeChallenge] = useState({
codeChallenge: "",
codeVerifier: ""
});

const onSuccess = (defaultVCStorageExpiryLimit: number = vcStorageExpiryLimitInTimes) => {
useEffect(() => {
if (state === RequestStatus.ERROR) {
props.setToastError(t("errorContent"));
}
if (response) {
if (state === RequestStatus.DONE) {
if (validateIfAuthServerSupportRequiredGrantTypes(response)) {
window.open(
api.authorization(
selectedIssuer.selected_issuer,
filteredCredentialConfig,
authorizationReqState,
codeChallenge,
response["authorization_endpoint"]
),
"_self",
"noopener"
);
} else {
props.setErrorObj({
code: "errors.authorizationGrantTypeNotSupportedByWallet.code",
message:
"errors.authorizationGrantTypeNotSupportedByWallet.message"
});
}
}
}
}, [response, state]);

const onSuccess = async (
defaultVCStorageExpiryLimit: number = vcStorageExpiryLimitInTimes
) => {
const state = generateRandomString();
const code_challenge: CodeChallengeObject = generateCodeChallenge(state);
addNewSession({
setAuthorizationRequestState(state);
setCodeChallenge(generateCodeChallenge(state));
setCredentialExpiry(false);

addNewSession({
selectedIssuer: selectedIssuer.selected_issuer,
certificateId: props.credentialId,
codeVerifier: state,
vcStorageExpiryLimitInTimes: isNaN(defaultVCStorageExpiryLimit) ? vcStorageExpiryLimitInTimes : defaultVCStorageExpiryLimit,
state: state,
vcStorageExpiryLimitInTimes: isNaN(defaultVCStorageExpiryLimit)
? vcStorageExpiryLimitInTimes
: defaultVCStorageExpiryLimit,
state: state
});
window.open(api.authorization(selectedIssuer.selected_issuer, props.credentialWellknown, filteredCredentialConfig, state, code_challenge), '_self', 'noopener');
}

return <React.Fragment>
<ItemBox index={props.index}
url={credentialObject.logo.url}
title={credentialObject.name}
onClick={() => {selectedIssuer.selected_issuer.qr_code_type === 'OnlineSharing' ? setCredentialExpiry(true) : onSuccess(-1)} } />
{ credentialExpiry &&
<DataShareExpiryModal onCancel={() => setCredentialExpiry(false)}
onSuccess={onSuccess}
credentialName={credentialObject.name}
credentialLogo={credentialObject.logo.url}/>
}
</React.Fragment>
}
const apiRequest = api.fetchAuthorizationServerWellknown(
props.credentialWellknown.authorization_servers[0]
);

await fetchRequest(
apiRequest.url(),
apiRequest.methodType,
apiRequest.headers()
);
};

const validateIfAuthServerSupportRequiredGrantTypes = (
authorizationServerWellknown: AuthServerWellknownObject
) => {
const supportedGrantTypes = ["authorization_code"];
let authorizationServerGrantTypes = ["authorization_code", "implicit"];

if ("grant_types_supported" in authorizationServerWellknown) {
authorizationServerGrantTypes =
authorizationServerWellknown["grant_types_supported"];
}

return authorizationServerGrantTypes.some((grantType: string) =>
supportedGrantTypes.includes(grantType)
);
};

return (
<React.Fragment>
<ItemBox
index={props.index}
url={credentialObject.logo.url}
title={credentialObject.name}
onClick={() => {
selectedIssuer.selected_issuer.qr_code_type ===
"OnlineSharing"
? setCredentialExpiry(true)
: onSuccess(-1);
}}
/>
{credentialExpiry && (
<DataShareExpiryModal
onCancel={() => setCredentialExpiry(false)}
onSuccess={onSuccess}
credentialName={credentialObject.name}
credentialLogo={credentialObject.logo.url}
/>
)}
</React.Fragment>
);
};
7 changes: 3 additions & 4 deletions inji-web/src/hooks/useFetch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,13 @@ export const useFetch = () => {
throw new Error();
}
setState(RequestStatus.DONE);
return await response.json();
const responseJson = await response.json();
setResponse(responseJson);
return responseJson;
} catch (e) {
setState(RequestStatus.ERROR);
setError("Error Happened");
}
};
return {state, error, response, fetchRequest};
}



8 changes: 7 additions & 1 deletion inji-web/src/locales/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,13 @@
"emptyContainerContent": "لم يتم العثور على بيانات الاعتماد. ",
"containerHeading": "قائمة أنواع الاعتمادات",
"containerSubHeading": "اختر بيانات الاعتماد، وأكد هويتك في الخطوة التالية.",
"errorContent": "الخدمة غير متاحة حالياً. "
"errorContent": "الخدمة غير متاحة حالياً.",
"errors": {
"authorizationGrantTypeNotSupportedByWallet": {
"title": "نوع المنحة غير مدعوم خطأ في التفويض!",
"message": "شكرًا على صبرك! نحن نواجه صعوبات تقنية في الوقت الحالي. يُرجى المحاولة مرة أخرى لاحقًا أو الاتصال بالمسؤول للحصول على مزيد من المساعدة!"
}
}
},
"RedirectionPage": {
"navigateButton": "اذهب إلى المنزل",
Expand Down
8 changes: 7 additions & 1 deletion inji-web/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,13 @@
"emptyContainerContent": "No Credentials found. Please refresh your browser window or try again later",
"containerHeading": "List of Credential Types",
"containerSubHeading": "Choose a credential, and confirm your identity in the next step.",
"errorContent": "The service is currently unavailable now. Please try again later."
"errorContent": "The service is currently unavailable now. Please try again later.",
"errors": {
"authorizationGrantTypeNotSupportedByWallet": {
"code": "Grant type not supported authorization error!",
"message": "Thanks for your patience! We're experiencing technical difficulties right now. Please try again later or contact the admin for further assistance!"
}
}
},
"RedirectionPage": {
"navigateButton": "Go To Home",
Expand Down
Loading
Loading