Skip to content

Commit b2ba00c

Browse files
authored
Merge pull request #82 from StreetSupport/staging
Merge staging into main
2 parents 1781c33 + 7de715b commit b2ba00c

File tree

4 files changed

+52
-3
lines changed

4 files changed

+52
-3
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ MONGODB_URI=mongodb+srv://...
7878
# Auth0
7979
AUTH0_DOMAIN='take it from https://manage.auth0.com/. For example: your-tenant.auth0.com'
8080
AUTH0_AUDIENCE='take it from https://manage.auth0.com/'
81+
AUTH0_CLIENT_ID='Application Client ID from https://manage.auth0.com/. Used for password change emails.'
8182
AUTH0_USER_DB_CONNECTION='take it from https://manage.auth0.com/'
8283
AUTH0_MANAGEMENT_CLIENT_ID='take it from https://manage.auth0.com/'
8384
AUTH0_MANAGEMENT_CLIENT_SECRET='take it from https://manage.auth0.com/'

src/config/auth0.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ dotenv.config();
55
const auth0Config = {
66
domain: process.env.AUTH0_DOMAIN,
77
audience: process.env.AUTH0_AUDIENCE,
8+
clientId: process.env.AUTH0_CLIENT_ID,
89
managementClientId: process.env.AUTH0_MANAGEMENT_CLIENT_ID,
910
managementClientSecret: process.env.AUTH0_MANAGEMENT_CLIENT_SECRET,
1011
managementAudience: process.env.AUTH0_MANAGEMENT_AUDIENCE,
@@ -19,6 +20,10 @@ if (!auth0Config.audience) {
1920
throw new Error('AUTH0_AUDIENCE is not set in .env file');
2021
}
2122

23+
if (!auth0Config.clientId) {
24+
throw new Error('AUTH0_CLIENT_ID is not set in .env file');
25+
}
26+
2227
if (!auth0Config.managementClientId) {
2328
throw new Error('AUTH0_MANAGEMENT_CLIENT_ID is not set in .env file');
2429
}

src/controllers/userController.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import ArchivedUser from '../models/archivedUserModel.js';
44
import { asyncHandler } from '../utils/asyncHandler.js';
55
import { decryptUserEmail, encryptEmail } from '../utils/encryption.js';
66
import { validateUserCreate, validateUserUpdate } from '../schemas/userSchema.js';
7-
import { createAuth0User, deleteAuth0User, blockAuth0User, unblockAuth0User, updateAuth0UserRoles } from '../services/auth0Service.js';
7+
import { createAuth0User, deleteAuth0User, blockAuth0User, unblockAuth0User, updateAuth0UserRoles, sendPasswordChangeEmail } from '../services/auth0Service.js';
88
import { sendSuccess, sendCreated, sendNotFound, sendBadRequest, sendInternalError, sendPaginatedSuccess, sendForbidden } from '../utils/apiResponses.js';
99
import { ROLE_PREFIXES, ROLES } from '../constants/roles.js';
1010
import Organisation from '../models/organisationModel.js';
@@ -261,6 +261,14 @@ const createUser = asyncHandler(async (req: Request, res: Response) => {
261261
return sendInternalError(res, 'Failed to create user in database');
262262
}
263263

264+
// Send password change email so user can set their password
265+
try {
266+
await sendPasswordChangeEmail(userData.Email);
267+
} catch (error) {
268+
console.error('Failed to send password change email:', error);
269+
// Non-blocking - user is created, they can use forgot password if email fails
270+
}
271+
264272
// Return user with decrypted email
265273
const userResponse = {
266274
...user.toObject(),

src/services/auth0Service.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,16 @@ export async function createAuth0User(
9999
const accessToken = await getAuth0ManagementToken();
100100

101101
// Build create user request - Auth0 will auto-generate user_id
102+
// We set email_verified: true because admins control user creation
103+
// We set verify_email: false to prevent Auth0 sending verification email
104+
// Instead, we send a password change email so users can set their password
102105
const createUserRequest: CreateAuth0UserRequest = {
103106
connection: connection,
104107
email: email,
105108
name: email, // Use email as name
106109
password: generateTempPassword(),
107-
email_verified: false,
108-
verify_email: true,
110+
email_verified: true,
111+
verify_email: false,
109112
app_metadata: {
110113
authorization: {
111114
roles: authClaims,
@@ -133,6 +136,38 @@ export async function createAuth0User(
133136
return createdUser;
134137
}
135138

139+
/**
140+
* Send password change email to user via Auth0 Authentication API
141+
* This allows new users to set their password after account creation
142+
* @param email - User email address
143+
*/
144+
export async function sendPasswordChangeEmail(email: string): Promise<void> {
145+
const domain = auth0Config.domain as string;
146+
const clientId = auth0Config.clientId as string;
147+
const connection = (auth0Config.userDbConnection as string) || 'Username-Password-Authentication';
148+
149+
if (!domain || !clientId) {
150+
throw new Error('AUTH0_DOMAIN or AUTH0_CLIENT_ID is not configured');
151+
}
152+
153+
const response = await fetch(`https://${domain}/dbconnections/change_password`, {
154+
method: HTTP_METHODS.POST,
155+
headers: {
156+
'Content-Type': 'application/json',
157+
},
158+
body: JSON.stringify({
159+
client_id: clientId,
160+
email: email,
161+
connection: connection,
162+
}),
163+
});
164+
165+
if (!response.ok) {
166+
const error = await response.text();
167+
throw new Error(`Failed to send password change email: ${error}`);
168+
}
169+
}
170+
136171
/**
137172
* Delete a user from Auth0
138173
* @param auth0UserId - Auth0 user ID (e.g., "auth0|123456")

0 commit comments

Comments
 (0)