Skip to content

Commit 66525b8

Browse files
Add Google and Angeleno Account SSO login (hackforla#1502)
Enable OIDC sign-in for City staff (Google) and external users (Angeleno Account), with demo mode for local development and DB support for external identity mapping.
1 parent 3e58be0 commit 66525b8

13 files changed

Lines changed: 1820 additions & 74 deletions

client/src/components/Authorization/Login.jsx

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useRef, useContext } from "react";
1+
import React, { useEffect, useState, useRef, useContext } from "react";
22
import UserContext from "../../contexts/UserContext";
33
import { Link, useParams, useNavigate, useLocation } from "react-router-dom";
44
import { createUseStyles, useTheme } from "react-jss";
@@ -19,6 +19,25 @@ const useStyles = createUseStyles(theme => ({
1919
justifyContent: "flex-end",
2020
margin: "16px auto"
2121
},
22+
ssoButtonContainer: {
23+
display: "flex",
24+
flexDirection: "column",
25+
gap: "12px",
26+
justifyContent: "center",
27+
margin: "16px auto"
28+
},
29+
divider: {
30+
alignItems: "center",
31+
display: "flex",
32+
gap: "12px",
33+
margin: "24px 0",
34+
"&::before, &::after": {
35+
background: "#d8d8d8",
36+
content: "''",
37+
flex: 1,
38+
height: "1px"
39+
}
40+
},
2241
warning: {
2342
color: "red",
2443
textAlign: "center",
@@ -45,6 +64,35 @@ const Login = () => {
4564
password: ""
4665
};
4766

67+
const getPostLoginPath = () => {
68+
if (projectId) return `/calculation/5/${projectId}`;
69+
if (redirectUrl) return `/${redirectUrl}`;
70+
return "/calculation/1/0";
71+
};
72+
73+
useEffect(() => {
74+
const completeSsoLogin = async () => {
75+
if (
76+
searchParams.get("angeleno") !== "success" &&
77+
searchParams.get("google") !== "success"
78+
) {
79+
return;
80+
}
81+
82+
const sessionResponse = await accountService.getSession();
83+
if (sessionResponse?.isSuccess) {
84+
userContext.updateAccount(sessionResponse.user);
85+
navigate(searchParams.get("next") || getPostLoginPath());
86+
} else {
87+
setErrorMsg(
88+
"SSO sign-in succeeded, but the TDM session could not be loaded."
89+
);
90+
}
91+
};
92+
93+
completeSsoLogin();
94+
}, []);
95+
4896
const loginSchema = Yup.object().shape({
4997
email: Yup.string()
5098
.email("Invalid email address format")
@@ -107,6 +155,14 @@ const Login = () => {
107155
}
108156
};
109157

158+
const handleAngelenoLogin = () => {
159+
accountService.startAngelenoLogin(getPostLoginPath());
160+
};
161+
162+
const handleGoogleLogin = () => {
163+
accountService.startGoogleLogin(getPostLoginPath());
164+
};
165+
110166
return (
111167
<ContentContainer>
112168
<div style={theme.typography.heading1}>
@@ -117,6 +173,25 @@ const Login = () => {
117173
</div>
118174
<br />
119175
<div className="auth-form">
176+
<div className={classes.ssoButtonContainer}>
177+
<Button
178+
id="cy-login-google"
179+
type="button"
180+
variant="primary"
181+
onClick={handleGoogleLogin}
182+
>
183+
Sign in with Google SSO (City staff)
184+
</Button>
185+
<Button
186+
id="cy-login-angeleno"
187+
type="button"
188+
variant="primary"
189+
onClick={handleAngelenoLogin}
190+
>
191+
Sign in with Angeleno Account (external users)
192+
</Button>
193+
</div>
194+
<div className={classes.divider}>or sign in with TDM credentials</div>
120195
<Formik
121196
initialValues={initialValues}
122197
validationSchema={loginSchema}

client/src/services/account.service.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,29 @@ export const login = async (email, password) => {
6464
}
6565
};
6666

67+
export const getSession = async () => {
68+
try {
69+
const response = await axios.get(baseUrl + "/session");
70+
return response.data;
71+
} catch (err) {
72+
console.error(err);
73+
}
74+
};
75+
76+
export const startAngelenoLogin = redirectPath => {
77+
const redirect = redirectPath || "/calculation/1/0";
78+
window.location.assign(
79+
`${baseUrl}/angeleno/login?redirect=${encodeURIComponent(redirect)}`
80+
);
81+
};
82+
83+
export const startGoogleLogin = redirectPath => {
84+
const redirect = redirectPath || "/calculation/1/0";
85+
window.location.assign(
86+
`${baseUrl}/google/login?redirect=${encodeURIComponent(redirect)}`
87+
);
88+
};
89+
6790
export const logout = async () => {
6891
const response = await axios.get(`${baseUrl}/logout`);
6992
return response;

server/.env.example

Lines changed: 77 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
# Instructions: Create a .env file in the server directory. Copy and paste values from Google Docs.
1+
# Instructions: Copy to server/.env and fill in secrets from your team vault or Google Docs.
22

33
# Node server settings
4-
PORT=
4+
PORT=5001
55
NODE_OPTIONS=
66

77
JWT_SECRET_KEY=
@@ -11,12 +11,15 @@ SENDGRID_API_KEY=
1111
EMAIL_PUBLIC_COMMENT_LA_CITY=
1212
EMAIL_PUBLIC_COMMENT_WEB_TEAM=
1313

14-
# These env settings are specific to running the web api server and app on localhost.
15-
CLIENT_URL=
16-
SERVER_URL=
14+
#########################################
15+
## Local development (default) ##
16+
#########################################
17+
18+
CLIENT_URL=http://localhost:3001
19+
SERVER_URL=http://localhost:5001
1720

18-
# User Test Accounts for Shared AWS Development Environment
19-
#(Used by ThunderClient for API Endpoint testing)
21+
# User Test Accounts for Shared Dev Environment
22+
# (Used by ThunderClient for API endpoint testing)
2023
USERTEST_EMAIL=
2124
USERTEST_PASSWORD=
2225
ROLESTEST_EMAIL=
@@ -30,36 +33,80 @@ SQL_SERVER_PORT=
3033
SQL_DATABASE_NAME=
3134
SQL_USER_NAME=
3235
SQL_PASSWORD=
33-
SQL_ENCRYPT=
36+
SQL_ENCRYPT=true
3437
SQL_TRUST_SERVER_CERTIFICATE=
3538
EMAIL_SENDER=
3639
APPLICATIONINSIGHTS_CONNECTION_STRING=
3740

41+
# Angeleno Account / OIDC (sandbox)
42+
# Set ANGELENO_DEMO_MODE=true for local demos without OIDC credentials.
43+
ANGELENO_DEMO_MODE=true
44+
ANGELENO_DEMO_EMAIL=
45+
ANGELENO_DEMO_FIRST_NAME=
46+
ANGELENO_DEMO_LAST_NAME=
47+
ANGELENO_DEMO_SUBJECT=
48+
ANGELENO_DEMO_PASSWORD=
49+
ANGELENO_ISSUER=https://login.sandbox.account.lacity.gov
50+
ANGELENO_CLIENT_ID=
51+
ANGELENO_CLIENT_SECRET=
52+
ANGELENO_REDIRECT_URI=http://localhost:5001/api/accounts/angeleno/callback
53+
ANGELENO_AUTHORIZATION_URL=https://login.sandbox.account.lacity.gov/authorize
54+
ANGELENO_TOKEN_URL=https://login.sandbox.account.lacity.gov/oauth/token
55+
ANGELENO_JWKS_URL=https://login.sandbox.account.lacity.gov/.well-known/jwks.json
56+
ANGELENO_SCOPE=openid profile email
57+
58+
# Google SSO / OIDC for internal City users
59+
# Set GOOGLE_SSO_DEMO_MODE=true for local demos without OAuth credentials.
60+
GOOGLE_SSO_DEMO_MODE=true
61+
GOOGLE_SSO_DEMO_EMAIL=
62+
GOOGLE_SSO_DEMO_FIRST_NAME=
63+
GOOGLE_SSO_DEMO_LAST_NAME=
64+
GOOGLE_SSO_DEMO_SUBJECT=
65+
GOOGLE_SSO_ISSUER=https://accounts.google.com
66+
GOOGLE_SSO_CLIENT_ID=
67+
GOOGLE_SSO_CLIENT_SECRET=
68+
GOOGLE_SSO_REDIRECT_URI=http://localhost:5001/api/accounts/google/callback
69+
GOOGLE_SSO_HOSTED_DOMAIN=lacity.org
70+
GOOGLE_SSO_AUTHORIZATION_URL=
71+
GOOGLE_SSO_TOKEN_URL=
72+
GOOGLE_SSO_JWKS_URL=
73+
GOOGLE_SSO_SCOPE=openid profile email
74+
3875
#########################################
39-
## Local Development Database Settings ##
76+
## Shared Dev Environment (Azure) ##
77+
## https://tdm-dev.azurewebsites.net ##
4078
#########################################
41-
42-
## Local Development Database - Windows Native
43-
# SQL_SERVER_NAME=
44-
# SQL_SERVER_INSTANCE=
45-
# SQL_SERVER_PORT=
46-
# SQL_DATABASE_NAME=
47-
# SQL_USER_NAME=
48-
# SQL_PASSWORD=
49-
# SQL_ENCRYPT=
50-
# SQL_TRUST_SERVER_CERTIFICATE=
51-
# EMAIL_SENDER=
52-
# APPLICATIONINSIGHTS_CONNECTION_STRING=
79+
# Use these values in Azure App Service env vars (not for local .env):
80+
#
81+
# CLIENT_URL=https://tdm-dev.azurewebsites.net
82+
# SERVER_URL=https://tdm-dev.azurewebsites.net
83+
# ANGELENO_DEMO_MODE=false
84+
# ANGELENO_REDIRECT_URI=https://tdm-dev.azurewebsites.net/api/accounts/angeleno/callback
85+
# GOOGLE_SSO_DEMO_MODE=false
86+
# GOOGLE_SSO_REDIRECT_URI=https://tdm-dev.azurewebsites.net/api/accounts/google/callback
87+
#
88+
# Register with Angeleno (sandbox):
89+
# Callback URL: https://tdm-dev.azurewebsites.net/api/accounts/angeleno/callback
90+
# Allowed logout URL: https://tdm-dev.azurewebsites.net/login
91+
# Allowed origins: https://tdm-dev.azurewebsites.net
92+
# Web origins: https://tdm-dev.azurewebsites.net
5393

5494
## Local Development Database - Docker Container
55-
## Example for SQL Server Express on Docker
56-
# SQL_SERVER_NAME=
95+
# SQL_SERVER_NAME=localhost
5796
# SQL_SERVER_INSTANCE=
97+
# SQL_SERVER_PORT=1434
98+
# SQL_DATABASE_NAME=tdmdev
99+
# SQL_USER_NAME=sa
100+
# SQL_PASSWORD=Dogfood1!
101+
# SQL_ENCRYPT=false
102+
# SQL_TRUST_SERVER_CERTIFICATE=true
103+
104+
## Local Development Database - Windows Native
105+
# SQL_SERVER_NAME=localhost
106+
# SQL_SERVER_INSTANCE=SQLEXPRESS
58107
# SQL_SERVER_PORT=
59-
# SQL_DATABASE_NAME=
60-
# SQL_USER_NAME=
61-
# SQL_PASSWORD=
62-
# SQL_ENCRYPT=
63-
# SQL_TRUST_SERVER_CERTIFICATE=
64-
# EMAIL_SENDER=
65-
# APPLICATIONINSIGHTS_CONNECTION_STRING=
108+
# SQL_DATABASE_NAME=tdmdev
109+
# SQL_USER_NAME=sa
110+
# SQL_PASSWORD=Dogfood1!
111+
# SQL_ENCRYPT=false
112+
# SQL_TRUST_SERVER_CERTIFICATE=true

0 commit comments

Comments
 (0)