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

Pcke and focused view #11

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
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
14 changes: 10 additions & 4 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"html2canvas": "^1.4.1",
"i18next": "^23.7.18",
"i18next-http-backend": "^2.4.2",
"js-sha256": "^0.11.0",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-bootstrap": "^2.10.0",
Expand Down
1 change: 1 addition & 0 deletions public/index.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://js-d.docusign.com/bundle.js"></script>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand Down
10 changes: 0 additions & 10 deletions public/locales/en/EmbeddedSigningTemplate.json

This file was deleted.

10 changes: 10 additions & 0 deletions public/locales/en/KnowYourCustomer.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,15 @@
"WarningModal": {
"Title": "Alert: Camera not found",
"Description": "It appears your device doesn’t have a camera! For demonstration purposes, we’ll provide a generic image."
},
"FocusedView": {
"Title": "Enter your personal information:",
"FullName": "Full name:",
"StreetAddress": "Street address:",
"City": "City:",
"State": "State:",
"ZipCode": "Zip code:",
"Phone": "Phone:",
"Email": "Email:"
}
}
143 changes: 60 additions & 83 deletions src/api/apiFactory.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/* eslint-disable no-param-reassign */
import { sha256 } from "js-sha256";
import { getAuthToken } from "../services/accountRepository";


const configureInterceptors = (api) => {
// Request interceptor for API calls
api.interceptors.request.use(
Expand All @@ -10,7 +12,10 @@ const configureInterceptors = (api) => {
"Content-Type": "application/json",
};
const accessTokenInfo = getAuthToken();
config.headers.Authorization = `Bearer ${accessTokenInfo.accessToken}`;

if (accessTokenInfo?.accessToken) {
config.headers.Authorization = `Bearer ${accessTokenInfo.accessToken}`;
}
return config;
},
(error) => {
Expand Down Expand Up @@ -116,25 +121,6 @@ export const createDocumentAPI = (
return response.status;
};

const createEnvelop = async (templateId, signerEmail, signerName) => {
const requestData = {
templateId,
templateRoles: [
{
email: signerEmail,
name: signerName,
roleName: "signer",
},
],
status: "created",
};
const response = await api.post(
`${accountBaseUrl}${eSignBase}/accounts/${accountId}/envelopes`,
requestData
);
return response.data.envelopeId;
};

const getDocumentId = async (envelopeId) => {
const response = await api.get(
`${accountBaseUrl}${eSignBase}/accounts/${accountId}/envelopes/${envelopeId}/docGenFormFields`
Expand Down Expand Up @@ -189,14 +175,13 @@ export const createDocumentAPI = (
createTemplate,
addDocumentToTemplate,
addTabsToTemplate,
createEnvelop,
getDocumentId,
updateFormFields,
sendEnvelop,
sendEnvelop
};
};

export const createEmbeddedSigningAPI = (
export const createFocusedViewAPI = (
axios,
eSignBase,
dsReturnUrl,
Expand All @@ -205,53 +190,16 @@ export const createEmbeddedSigningAPI = (
) => {
const api = createAPI(axios);

const createEnvelope = async (htmlDoc, signer) => {
const requestData = {
emailSubject:
process.env.REACT_APP_EMBEDDED_DOCUMENT_TEMPLATE_EMAIL_SUBJECT,
description: process.env.REACT_APP_EMBEDDED_DOCUMENT_TEMPLATE_DESCRIPTION,
name: process.env.REACT_APP_EMBEDDED_DOCUMENT_TEMPLATE_NAME,
shared: false,
status: "sent",
recipients: {
signers: [
{
email: signer.email,
name: signer.name,
recipientId: "1",
clientUserId: 1000,
roleName: "signer",
routingOrder: "1",
},
],
},
documents: [
{
name: process.env.REACT_APP_EMBEDDED_DOCUMENT_NAME,
documentId: 1,
htmlDefinition: {
source: htmlDoc,
},
},
],
};

const createEnvelope = async (requestData) => {
const response = await api.post(
`${accountBaseUrl}${eSignBase}/accounts/${accountId}/envelopes`,
requestData
);
return response.data.envelopeId;
};

const embeddedSigningCeremony = async (envelopeId, signer) => {
const requestData = {
returnUrl: dsReturnUrl,
authenticationMethod: "None",
clientUserId: 1000,
email: signer.email,
userName: signer.name,
};

const getRecipientView = async (envelopeId, requestData) => {
const response = await api.post(
`${accountBaseUrl}${eSignBase}/accounts/${accountId}/envelopes/${envelopeId}/views/recipient`,
requestData
Expand All @@ -260,30 +208,34 @@ export const createEmbeddedSigningAPI = (
return response.data.url;
};

const embeddedSigning = async (signer, template, onPopupIsBlocked) => {
const envelopeId = await createEnvelope(template, signer);
const url = await embeddedSigningCeremony(envelopeId, signer);

const signingWindow = window.open(url, "_blank");
const newTab = signingWindow;
if (!newTab || newTab.closed || typeof newTab.closed === "undefined") {
// POPUP BLOCKED
onPopupIsBlocked();
return false;
}
signingWindow.focus();
return signingWindow;
};

return {
embeddedSigning,
getRecipientView,
createEnvelope
};
};

export const generateCodeVerifier = () => {
const randomValues = new Uint8Array(32);
window.crypto.getRandomValues(randomValues);
return btoa(String.fromCharCode.apply(null, randomValues))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
};

export const generateCodeChallenge = (codeVerifier) => {
const hash = sha256.arrayBuffer(codeVerifier);
const hashArray = Array.from(new Uint8Array(hash));
return btoa(String.fromCharCode.apply(null, hashArray))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
};

export const createAuthAPI = (
axios,
serviceProvider,
implicitGrantPath,
authPath,
userInfoPath,
eSignBase,
scopes,
Expand All @@ -292,15 +244,21 @@ export const createAuthAPI = (
) => {
const api = createAPI(axios);

const codeVerifier = generateCodeVerifier();
const codeChallenge = generateCodeChallenge(codeVerifier);

const login = async (nonce, onPopupIsBlocked) => {
const url =
`${serviceProvider}${implicitGrantPath}` +
`?response_type=token` +
`${serviceProvider}${authPath}` +
`?response_type=code` +
`&scope=${scopes}` +
`&client_id=${clientId}` +
`&redirect_uri=${returnUrl}` +
`&state=${nonce}`;
`&state=${nonce}` +
`&code_challenge_method=S256` +
`&code_challenge=${codeChallenge}`;
const loginWindow = window.open(url, "_blank");

const newTab = loginWindow;
if (!newTab || newTab.closed || typeof newTab.closed === "undefined") {
// POPUP BLOCKED
Expand All @@ -311,6 +269,25 @@ export const createAuthAPI = (
return { window: loginWindow, nonce };
};

const obtainAccessToken = async (code) => {
const requestData = {
code: `${code}`,
code_verifier: `${codeVerifier}`,
grant_type: "authorization_code",
client_id: `${clientId}`,
code_challenge_method: "S256",
redirect_uri: `${returnUrl}`

};

const response = await api.post(
`${serviceProvider}/oauth/token`,
requestData
);

return response.data;
}

const fetchUserInfo = async () => {
const response = await api.get(`${serviceProvider}${userInfoPath}`);
return response.data;
Expand All @@ -323,5 +300,5 @@ export const createAuthAPI = (
return response.data.externalAccountId;
};

return { login, fetchUserInfo, fetchExternalAccountId };
return { login, fetchUserInfo, fetchExternalAccountId, obtainAccessToken };
};
8 changes: 4 additions & 4 deletions src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import axios from "axios";
import {
createAuthAPI,
createDocumentAPI,
createEmbeddedSigningAPI,
createFocusedViewAPI,
} from "./apiFactory";

const authenticationAPI = createAuthAPI(
Expand All @@ -24,13 +24,13 @@ const initDocumentAPI = (baseAccountUrl, accountId) =>
accountId
);

const initEmbeddedSigningAPI = (baseAccountUrl, accountId) =>
createEmbeddedSigningAPI(
const initFocusedViewAPI = (baseAccountUrl, accountId) =>
createFocusedViewAPI(
axios,
process.env.REACT_APP_ESIGN_BASE,
process.env.REACT_APP_DS_RETURN_URL,
baseAccountUrl,
accountId
);

export { authenticationAPI, initDocumentAPI, initEmbeddedSigningAPI };
export { authenticationAPI, initDocumentAPI, initFocusedViewAPI };
2 changes: 1 addition & 1 deletion src/components/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Navbar } from "react-bootstrap";
import PropTypes from "prop-types";
import logo from "../assets/img/logo.svg";
import * as accountRepository from "../services/accountRepository";
import { logout } from "../hooks/useImplicitGrantAuth";
import { logout } from "../hooks/usePckeAuth";
import useBreakpoint, { SIZE_MD, SIZE_SM } from "../hooks/useBreakpoint";
import whiteToggleIcon from "../assets/img/white-links.png";
import blackToggleIcon from "../assets/img/black-links.png";
Expand Down
23 changes: 15 additions & 8 deletions src/hooks/useImplicitGrantAuth.js → src/hooks/usePckeAuth.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,24 +66,30 @@ export const useImplicitGrantAuth = (onError, onSuccess) => {
userEmail: userInfo.email,
});

const parseHash = (hash) => {
const items = hash.split(/=|&/);
const parseSearch = (search) => {
const queryString = search.startsWith('?') ? search.slice(1) : search;
const items = queryString.split(/=|&/);

let i = 0;
const response = {};

while (i + 1 < items.length) {
response[items[i]] = items[i + 1];
response[decodeURIComponent(items[i])] = decodeURIComponent(items[i + 1]);
i += 2;
}

return response;
};


const handleMessage = async (data) => {
// close the browser tab used for OAuth
const handleMessage = async () => {
if (loginResult.current.window) {
loginResult.current.window.close();
}
const hash = data.hash.substring(1); // remove the #
const response = parseHash(hash);

const response = parseSearch(loginResult.current.window.location.search);

const obtainAccessTokenResponse = await authenticationAPI.obtainAccessToken(response.code);

const newState = response.state;
if (newState !== loginResult.current.nonce) {
Expand All @@ -93,8 +99,9 @@ export const useImplicitGrantAuth = (onError, onSuccess) => {
});
return;
}

const accessTokenInfo = {
accessToken: response.access_token,
accessToken: obtainAccessTokenResponse.access_token,
accessTokenExpires: new Date(Date.now() + response.expires_in * 1000),
};
accountRepository.setAuthToken(accessTokenInfo);
Expand Down
Loading