Skip to content

Commit da5547e

Browse files
author
Theodore Li
committed
Add new admin dashboard
1 parent a96d52b commit da5547e

3 files changed

Lines changed: 176 additions & 5 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Handler that will be called during the execution of a PostLogin flow.
3+
*
4+
* @param {Event} event - Details about the user and the context in which they are logging in.
5+
* @param {PostLoginAPI} api - Interface whose methods can be used to change the behavior of the login.
6+
*/
7+
exports.onExecutePostLogin = async (event, api) => {
8+
try {
9+
const isAdminApp = event.client && event.client.metadata && event.client.metadata.role === "ADMIN";
10+
11+
if (isAdminApp) {
12+
const hasUserRole = event.client.metadata?.role == "ADMIN";
13+
14+
if (!hasUserRole) {
15+
console.log(`Admin access denied for user without ADMIN role: ${event.user.email}`);
16+
api.access.deny("Access denied. You must have ADMIN role to access this application.");
17+
return;
18+
}
19+
20+
const email = event.user.email;
21+
if (!email || !email.endsWith('@zenobiapay.com')) {
22+
console.log(`Admin access denied for non-zenobiapay.com email: ${email}`);
23+
api.access.deny("Access denied. Admin access requires a zenobiapay.com email address.");
24+
return;
25+
}
26+
27+
// Check if MFA (2FA) was completed during this authentication
28+
if (!event.authentication?.methods.some(method => method.name === "mfa")) {
29+
console.log(`Admin access denied for user without MFA: ${email}`);
30+
// Trigger MFA challenge
31+
api.multifactor.enable("any", { allowRememberBrowser: false });
32+
return;
33+
}
34+
35+
console.log(`Admin access granted for user with MFA: ${email}`);
36+
}
37+
} catch (err) {
38+
console.log(`Got error during admin authorization: ${err}`);
39+
}
40+
};

terraform/auth0/actions/credentials-exchange.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@ exports.onExecuteCredentialsExchange = async (event, api) => {
22
try {
33
const userRole = event.client.metadata?.role;
44
if (userRole) {
5+
if (userRole === "ADMIN") {
6+
const email = event.user?.email;
7+
if (!email || !email.endsWith('@zenobiapay.com')) {
8+
console.log(`Admin access denied for non-zenobiapay.com email: ${email}`);
9+
api.access.deny('Admin role requires a zenobiapay.com email address');
10+
return;
11+
}
12+
console.log(`Admin access granted for zenobiapay.com email: ${email}`);
13+
}
14+
515
console.log(`Found user role ${userRole}, adding to claims`);
616
api.accessToken.setCustomClaim("role", userRole);
717
} else {
@@ -13,7 +23,7 @@ exports.onExecuteCredentialsExchange = async (event, api) => {
1323
console.log(`Found merchant sub ${m2mSub}, adding to claims`);
1424
api.accessToken.setCustomClaim("m2mSub", m2mSub);
1525
} else {
16-
console.log("No user role found, skipping adding to claim");
26+
console.log("No m2m sub found, skipping adding to claim");
1727
}
1828
} catch (err) {
1929
console.log(`Got err ${err}`)

terraform/main.tf

Lines changed: 125 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ resource "auth0_client" "zenobia_app" {
4040
"https://beta-dashboard.zenobiapay.com",
4141
"http://localhost:3000"
4242
]
43+
# Disable Username-Password Authentication
44+
is_first_party = true
45+
oidc_conformant = true
46+
jwt_configuration {
47+
alg = "RS256"
48+
}
49+
# Restrict to social connections only (Google OAuth)
50+
client_metadata = {
51+
disable_username_password_authentication = "true"
52+
}
4353
}
4454

4555
resource "auth0_client_grant" "zenobia_app_client_grant" {
@@ -114,12 +124,107 @@ resource "auth0_trigger_actions" "bind_credentials_exchange_registration" {
114124
}
115125
}
116126

117-
output "client_id" {
118-
value = auth0_client.zenobia_app.client_id
127+
# New Auth0 client for admin interface
128+
resource "auth0_client" "zenobia_admin_app" {
129+
name = "Zenobia Admin"
130+
app_type = "regular_web"
131+
logo_uri = "https://zenobiapay.com/android-chrome-192x192.png"
132+
callbacks = var.ENVIRONMENT == "prod" ? [
133+
"https://admin.zenobiapay.com/callback",
134+
"https://admin.zenobiapay.com/login",
135+
"http://localhost:3001/callback",
136+
"http://localhost:3001/login"
137+
] : [
138+
"https://beta-admin.zenobiapay.com/callback",
139+
"https://beta-admin.zenobiapay.com/login",
140+
"http://localhost:3001/callback",
141+
"http://localhost:3001/login"
142+
]
143+
allowed_logout_urls = var.ENVIRONMENT == "prod" ? [
144+
"https://admin.zenobiapay.com",
145+
"http://localhost:3001"
146+
] : [
147+
"https://beta-admin.zenobiapay.com",
148+
"http://localhost:3001"
149+
]
150+
jwt_configuration {
151+
alg = "RS256"
152+
}
153+
# Add metadata to identify this as an admin application
154+
client_metadata = {
155+
role = "ADMIN"
156+
}
157+
# Enable required grant types for refresh tokens
158+
grant_types = [
159+
"authorization_code",
160+
"implicit",
161+
"refresh_token"
162+
]
163+
# Enable MFA (2FA) for this application
164+
initiate_login_uri = var.ENVIRONMENT == "prod" ? "https://admin.zenobiapay.com/login" : "https://beta-admin.zenobiapay.com/login"
165+
refresh_token {
166+
rotation_type = "rotating"
167+
expiration_type = "expiring"
168+
leeway = 0
169+
token_lifetime = 2592000 # 30 days
170+
idle_token_lifetime = 1296000 # 15 days
171+
infinite_token_lifetime = false
172+
infinite_idle_token_lifetime = false
173+
}
174+
# Require MFA
175+
organization_usage = "require"
176+
oidc_conformant = true
119177
}
120178

121-
output "api_identifier" {
122-
value = auth0_resource_server.zenobia_api.identifier
179+
resource "auth0_client_grant" "zenobia_admin_app_client_grant" {
180+
client_id = auth0_client.zenobia_admin_app.id
181+
audience = "https://admin.zenobiapay.com"
182+
scopes = []
183+
}
184+
185+
resource "auth0_client_credentials" "zenobia_admin_app_credentials" {
186+
client_id = auth0_client.zenobia_admin_app.id
187+
authentication_method = "none"
188+
}
189+
190+
resource "auth0_resource_server" "zenobia_admin_api" {
191+
name = "Zenobia Admin API"
192+
identifier = "https://admin.zenobiapay.com"
193+
signing_alg = "RS256"
194+
token_lifetime = 36000
195+
skip_consent_for_verifiable_first_party_clients = true
196+
}
197+
198+
resource "auth0_action" "admin_authorization" {
199+
name = "Admin-Authorization"
200+
runtime = "node22"
201+
deploy = true
202+
supported_triggers {
203+
id = "post-login"
204+
version = "v3"
205+
}
206+
207+
code = file("${path.module}/auth0/actions/admin-authorization.js")
208+
209+
dependencies {
210+
name = "auth0"
211+
version = "2.44.0"
212+
}
213+
}
214+
215+
resource "auth0_trigger_actions" "bind_admin_authorization" {
216+
trigger = "post-login"
217+
218+
actions {
219+
id = auth0_action.admin_authorization.id
220+
display_name = auth0_action.admin_authorization.name
221+
}
222+
223+
# Keep the existing post-login action
224+
actions {
225+
id = auth0_action.user_login_webhook.id
226+
display_name = auth0_action.user_login_webhook.name
227+
}
123228
}
124229

125230
resource "auth0_connection" "google_oauth2" {
@@ -139,3 +244,19 @@ resource "auth0_connection_clients" "google_oauth2_clients" {
139244
enabled_clients = [auth0_client.zenobia_app.id]
140245
connection_id = auth0_connection.google_oauth2[0].id
141246
}
247+
248+
output "client_id" {
249+
value = auth0_client.zenobia_app.client_id
250+
}
251+
252+
output "api_identifier" {
253+
value = auth0_resource_server.zenobia_api.identifier
254+
}
255+
256+
output "admin_client_id" {
257+
value = auth0_client.zenobia_admin_app.client_id
258+
}
259+
260+
output "admin_api_identifier" {
261+
value = auth0_resource_server.zenobia_admin_api.identifier
262+
}

0 commit comments

Comments
 (0)