Skip to content

Commit 6c5d3fe

Browse files
Merge pull request #9 from AmshikaH/main
Add corporate banking capabilities
2 parents b0252d9 + 45980d5 commit 6c5d3fe

29 files changed

+2730
-214
lines changed

README.md

Lines changed: 12 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -3,176 +3,22 @@
33
# Instructions to use the application
44

55
1. Register an organization with Asgardeo.
6-
2. Create [custom attributes](https://wso2.com/asgardeo/docs/guides/users/attributes/manage-attributes/) named `accountType` and `businessName`. Add the accountType and country attributes to the profile scope.
6+
2. Create [custom attributes](https://wso2.com/asgardeo/docs/guides/users/attributes/manage-attributes/) named `accountType` and `businessName`. Add the businessName, accountType and country attributes to the profile scope.
77
3. Create another [custom attribute](https://wso2.com/asgardeo/docs/guides/users/attributes/manage-attributes/) with the name `isFirstLogin`.
88
4. Enable the [Attribute Update Verification](https://wso2.com/asgardeo/docs/guides/users/attributes/user-attribute-change-verification/) for user email.
99
5. Create a SPA application.
10-
* Enable the `Code` and `Refresh Grant` types
10+
* Navigate to the "Shared Access" tab and share the application with all organizations.
11+
* Enable the `Code`, `Refresh Grant` and `Organization Switch` types.
12+
* Note that the organization switch grant type is available only after shared access is enabled.
1113
* Add authorize redirect URL: `http://localhost:5173` and allowed origin: `http://localhost:5173`
12-
* Add the `country` and `accountType` to Profile scope navigating to `User Attributes & Stores` -> `Attributes` -> `OpenId Connect` -> `Scopes` -> `Profile` -> `New Attribute`.
14+
* Add the `mobile`, `country`, `email` and `accountType` to Profile scope navigating to `User Attributes & Stores` -> `Attributes` -> `OpenId Connect` -> `Scopes` -> `Profile` -> `New Attribute`.
1315
* Enable the following scopes and attributes within the client application created.
14-
* `Profile - Country, First Name, Last Name, Username, Birth Date, AccountType; Email - email; Phone - telephone; Address - country.`
16+
* `Profile - Country, First Name, Last Name, Username, Birth Date, AccountType, Business Name, Email; Email - email; Phone - telephone; Address - country.`
1517
6. Enable the following authenticators within the client application:
1618
* `Identifier First` - First Step
1719
* `Username and Password`, `Passkey` - Second Step
1820
* `Totp` and `Email OTP` - Third Step
19-
7. Configure the following conditional authentication script (Replace the `<NODE_SERVER_BASE_PATH>` with server URL):
20-
```js
21-
var moneyTransferThres = 10000;
22-
var riskEndpoint = "<NODE_SERVER_BASE_PATH>/risk"
23-
24-
var onLoginRequest = function(context) {
25-
26-
var isMoneyTransfer = context.request.params.action && context.request.params.action[0] === "money-transfer";
27-
28-
if (isMoneyTransfer) {
29-
Log.info("Custom param:" + context.request.params.action[0]);
30-
Log.info("Custom param:" + context.request.params.transfer_amount[0]);
31-
var amount = parseInt(context.request.params.transfer_amount[0] || -1);
32-
33-
executeStep(1);
34-
if (amount > moneyTransferThres) {
35-
executeStep(4, {
36-
stepOptions: {
37-
forceAuth: 'true'
38-
}
39-
}, {});
40-
}
41-
42-
} else {
43-
44-
executeStep(1, {
45-
onSuccess: function(context) {
46-
var user = context.steps[1].subject;
47-
var accountType = user.localClaims["http://wso2.org/claims/accountType"];
48-
var country = user.localClaims["http://wso2.org/claims/country"];
49-
Log.info("Account Type: " + accountType);
50-
Log.info("Country: " + country);
51-
var ipAddress = context.request.ip;
52-
Log.info("IP Address: " + ipAddress);
53-
var requestPayload = {
54-
ip: ipAddress,
55-
country: country,
56-
};
57-
if (accountType === "Personal") {
58-
httpPost(riskEndpoint, requestPayload, {
59-
"Accept": "application/json"
60-
}, {
61-
onSuccess: function(context, data) {
62-
Log.info("Successfully invoked the external API.");
63-
Log.info("Logging data for country risk: " + data.hasRisk);
64-
65-
if (data.hasRisk === false) {
66-
executeStep(2, {
67-
authenticationOptions: [{
68-
authenticator: 'FIDOAuthenticator'
69-
}, {
70-
authenticator: 'BasicAuthenticator'
71-
}]
72-
}, {
73-
onSuccess: function(context) {
74-
var user = context.currentKnownSubject;
75-
var sessions = getUserSessions(user);
76-
Log.info(sessions);
77-
if (sessions.length > 0) {
78-
executeStep(3, {
79-
authenticationOptions: [{
80-
authenticator: 'email-otp-authenticator'
81-
}]
82-
}, {});
83-
}
84-
}
85-
});
86-
} else {
87-
executeStep(2, {
88-
authenticationOptions: [{
89-
authenticator: 'FIDOAuthenticator'
90-
}, {
91-
authenticator: 'BasicAuthenticator'
92-
}],
93-
}, {});
94-
Log.info("In 2nd step for Personal Accounts");
95-
96-
executeStep(3, {
97-
authenticationOptions: [{
98-
authenticator: 'email-otp-authenticator'
99-
}]
100-
}, {});
101-
}
102-
},
103-
onFail: function(context, data) {
104-
Log.error("Failed to invoke risk API");
105-
fail();
106-
}
107-
});
108-
} else if (accountType === "Business") {
109-
Log.info("In second step for Business");
110-
111-
executeStep(2, {
112-
authenticationOptions: [{
113-
authenticator: 'BasicAuthenticator'
114-
}]
115-
}, {});
116-
var preferredClaimURI = "http://wso2.org/claims/identity/preferredMFAOption";
117-
var preferredClaim = user.localClaims[preferredClaimURI];
118-
119-
if (preferredClaim != null) {
120-
Log.info("Preferred Claim Available");
121-
122-
var jsonObj = JSON.parse(preferredClaim);
123-
var authenticationOption = jsonObj.authenticationOption;
124-
Log.info("preferredClaim authenticationOption " + authenticationOption);
125-
executeStep(3, {
126-
authenticationOptions: [{
127-
authenticator: authenticationOption
128-
}],
129-
}, {});
130-
} else {
131-
Log.info("Preferred claim not available and in 3rd step");
132-
executeStep(3, {
133-
authenticationOptions: [{
134-
authenticator: 'totp'
135-
}, {
136-
authenticator: 'email-otp-authenticator'
137-
}]
138-
}, {
139-
onSuccess: function(context) {
140-
var preferredClaimURI = "http://wso2.org/claims/identity/preferredMFAOption";
141-
Log.info("3rd step successful");
142-
var user = context.steps[3].subject;
143-
var isFirstLogin = user.localClaims["http://wso2.org/claims/isFirstLogin"];
144-
Log.info("User isFirstLogin claim:" + isFirstLogin);
145-
if (isFirstLogin === "false") {
146-
var authenticatorName = context.steps[3].authenticator;
147-
var preferredMFA = {
148-
authenticationOption: authenticatorName
149-
};
150-
user.localClaims[preferredClaimURI] = JSON.stringify(preferredMFA);
151-
Log.info("Preferred MFA set from second login for user" + user.username + " as " + user.localClaims[preferredClaimURI]);
152-
} else {
153-
user.localClaims["http://wso2.org/claims/isFirstLogin"] = false;
154-
Log.info("User logged in for the first time. Setting isFirstLogin to false");
155-
}
156-
}
157-
});
158-
}
159-
}
160-
},
161-
onFail: function(context) {
162-
Log.info('User not found');
163-
var parameterMap = {
164-
'errorCode': 'login_failed',
165-
'errorMessage': 'login could not be completed',
166-
"errorURI": 'https://localhost:9443/authenticationendpoint/login.jsp'
167-
};
168-
fail(parameterMap);
169-
170-
}
171-
});
172-
}
173-
};
174-
175-
```
21+
7. Configure the conditional authentication script (Replace the `<NODE_SERVER_BASE_PATH>` with server URL) with the one found at conditional-auth-script.js.
17622
8. Create a standard web application.
17723
9. Navigate to the "Shared Access" tab and share the application with all organizations.
17824
10. Enable the following grant types:
@@ -191,6 +37,10 @@ redirect url: `https://localhost:5003`, allowed origin: `https://localhost:5003
19137
```
19238
internal_organization_create internal_organization_view internal_organization_update internal_organization_delete
19339
```
40+
- OAuth2 Introspection API
41+
```
42+
internal_oauth2_introspect
43+
```
19444
- Organization APIs:
19545
- SCIM2 Users API with the scopes:
19646
```
@@ -201,7 +51,7 @@ redirect url: `https://localhost:5003`, allowed origin: `https://localhost:5003
20151
internal_org_user_mgt_view internal_org_role_mgt_delete internal_org_role_mgt_create internal_org_role_mgt_update internal_org_role_mgt_view
20252
```
20353
204-
13. Navigate to the Roles tab and create an application role named `Business Administrator` with the permissions for the SCIM2 Users and SCIM2 Roles organization APIs.
54+
13. Navigate to the Roles tab and create an application role named `Business Administrator` with the permissions for the SCIM2 Users and SCIM2 Roles organization APIs. Also, create roles `Manager`, `Auditor` and `Member`.
20555
14. Navigate to Connections -> Passkey Setup -> Add the Trusted Origins: `http://localhost:5173` and enable `Allow Passkey usernameless authentication` option.
20656
20757
15. Configure [Onfido identity verification](https://wso2.com/asgardeo/docs/guides/identity-verification/add-identity-verification-with-onfido/) for your organization.

app/global.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ interface Window {
2020
APP_BASE_URL: string;
2121
ASGARDEO_BASE_URL: string;
2222
APP_CLIENT_ID: string;
23+
APP_NAME: string;
2324
DISABLED_FEATURES: string[];
2425
TRANSFER_THRESHOLD: number;
2526
IDENTITY_VERIFICATION_PROVIDER_ID: string;

app/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,17 @@
2020
"@fortawesome/free-regular-svg-icons": "^6.7.2",
2121
"@fortawesome/free-solid-svg-icons": "^6.7.2",
2222
"@fortawesome/react-fontawesome": "^0.2.2",
23+
"@mui/icons-material": "^6.5.0",
2324
"@mui/material": "^6.4.5",
25+
"@mui/x-data-grid": "^8.11.1",
2426
"@vitejs/plugin-react": "^4.3.4",
2527
"axios": "^1.8.1",
2628
"notistack": "^3.0.2",
2729
"onfido-sdk-ui": "^14.43.0",
2830
"prop-types": "^15.8.1",
2931
"qrcode.react": "^4.2.0",
3032
"react": "^19.0.0",
33+
"react-bootstrap": "^2.10.10",
3134
"react-dom": "^19.0.0",
3235
"react-router": "^7.3.0",
3336
"vite": "^6.1.1",

app/public/config.example.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ window.config = {
55
ASGARDEO_BASE_URL: "",
66
ORGANIZATION_NAME: "",
77
APP_CLIENT_ID: "",
8+
APP_NAME: "",
89
DISABLED_FEATURES: [],
910
TRANSFER_THRESHOLD: 10000,
1011
IDENTITY_VERIFICATION_PROVIDER_ID: "",

app/src/App.jsx

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
* under the License.
1717
*/
1818

19-
import { useAsgardeo, SignedIn, SignOutButton, SignedOut, SignInButton } from "@asgardeo/react";
19+
import { useAsgardeo, SignedIn, SignOutButton, SignedOut, SignInButton, useUser } from "@asgardeo/react";
2020
import { lazy, Suspense, useState } from "react";
2121
import {
2222
BrowserRouter as Router,
@@ -26,7 +26,7 @@ import {
2626
NavLink
2727
} from "react-router";
2828
import { SnackbarProvider } from "notistack";
29-
import { ROUTES, SITE_SECTIONS } from "./constants/app-constants";
29+
import { ACCOUNT_TYPES, ROUTES, SITE_SECTIONS } from "./constants/app-constants";
3030
import PersonalBankingPage from "./pages/personal-banking";
3131
import RegisterAccountPage from "./pages/register-account";
3232
import UserProfilePage from "./pages/user-profile";
@@ -37,10 +37,13 @@ import "./assets/css/style.scss";
3737
import { BankAccountProvider } from "./context/bank-account-provider";
3838
import IdentityVerificationPage from "./pages/identity-verification";
3939
import { IdentityVerificationProvider } from "./context/identity-verification-provider";
40+
import { Dropdown, DropdownButton } from "react-bootstrap";
41+
import BusinessProfilePage from "./pages/business-profile";
4042

4143
const App = () => {
42-
const { isSignedIn, signIn, signOut } = useAsgardeo();
44+
const { isSignedIn } = useAsgardeo();
4345
const [ siteSection, setSiteSection ] = useState("");
46+
const { profile} = useUser();
4447

4548
const TransferFundsPage = lazy(() => import("./pages/transfer-funds"));
4649
const TransferFundsVerifyPage = lazy(() => import("./pages/transfer-funds-verify"));
@@ -70,7 +73,7 @@ const App = () => {
7073
</span>
7174
<span>
7275
<SignedIn>
73-
<SignOutButton className="login_link">Logout</SignOutButton>
76+
<SignOutButton className="logout_link">Logout</SignOutButton>
7477
</SignedIn>
7578

7679
<SignedOut>
@@ -79,7 +82,23 @@ const App = () => {
7982
Open an account
8083
</span>
8184
</Link>
82-
<SignInButton className="login_link">Login</SignInButton>
85+
<span className="divider">|</span>
86+
<DropdownButton
87+
id="dropdown-custom-components"
88+
title="Login"
89+
autoClose={false}
90+
>
91+
<Dropdown.Item as="div" className="dropdown-item-custom dropdown-item-custom-top">
92+
<SignInButton className="login_link" signInOptions={{ loginType: 'Personal' }}>
93+
Personal Login
94+
</SignInButton>
95+
</Dropdown.Item>
96+
<Dropdown.Item as="div" className="dropdown-item-custom">
97+
<SignInButton className="login_link" signInOptions={{ loginType: 'Business' }}>
98+
Business Login
99+
</SignInButton>
100+
</Dropdown.Item>
101+
</DropdownButton>
83102
</SignedOut>
84103
</span>
85104
</div>
@@ -166,11 +185,20 @@ const App = () => {
166185
{ isSignedIn &&
167186
<Route path={ ROUTES.USER_PROFILE } element={ <UserProfilePage setSiteSection={ setSiteSection } /> } />
168187
}
188+
{ isSignedIn &&
189+
<Route path={ ROUTES.BUSINESS_PROFILE } element={ <BusinessProfilePage setSiteSection={ setSiteSection } /> } />
190+
}
169191
{/* <Route path="/" element={ <Navigate to={ ROUTES.PERSONAL_BANKING } setSiteSection={ setSiteSection } /> } /> */}
170192
<Route path="/" element={
171193
isSignedIn ?
172194
(
173-
<UserProfilePage setSiteSection={ setSiteSection } />
195+
<>
196+
{profile && profile["urn:scim:schemas:extension:custom:User"].accountType === ACCOUNT_TYPES.BUSINESS ? (
197+
<BusinessProfilePage setSiteSection={ setSiteSection } />
198+
) : (
199+
<UserProfilePage setSiteSection={ setSiteSection } />
200+
)}
201+
</>
174202
) : (
175203
<PersonalBankingPage setSiteSection={ setSiteSection } />
176204
)

app/src/api/profile.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export const closeAccount = (token) => {
2525
})
2626
};
2727

28+
export const closeBusinessAccount = (businessName) => {
29+
return axiosClient.delete(`/close-business-account?businessName=${businessName}`);
30+
};
31+
2832
export const resetPassword = (username, currentPassword, newPassword) => {
2933
// In case the password contains non-ascii characters, converting to valid ascii format.
3034
const encoder = new TextEncoder();

0 commit comments

Comments
 (0)