diff --git a/src/lib/routes/admin-api/user-admin.ts b/src/lib/routes/admin-api/user-admin.ts index 147253e200e..b24276ca8d9 100644 --- a/src/lib/routes/admin-api/user-admin.ts +++ b/src/lib/routes/admin-api/user-admin.ts @@ -10,6 +10,9 @@ import ResetTokenService from '../../services/reset-token-service'; import { IUnleashServices } from '../../types/services'; import SessionService from '../../services/session-service'; import { IAuthRequest } from '../unleash-types'; +import SettingService from '../../services/setting-service'; +import { SimpleAuthSettings } from '../../server-impl'; +import { simpleAuthKey } from '../../types/settings/simple-auth-settings'; interface ICreateUserBody { username: string; @@ -32,6 +35,10 @@ export default class UserAdminController extends Controller { private sessionService: SessionService; + private settingService: SettingService; + + readonly unleashUrl: string; + constructor( config: IUnleashConfig, { @@ -40,6 +47,7 @@ export default class UserAdminController extends Controller { emailService, resetTokenService, sessionService, + settingService, }: Pick< IUnleashServices, | 'userService' @@ -47,15 +55,18 @@ export default class UserAdminController extends Controller { | 'emailService' | 'resetTokenService' | 'sessionService' + | 'settingService' >, ) { super(config); this.userService = userService; this.accessService = accessService; this.emailService = emailService; - this.logger = config.getLogger('routes/user-controller.ts'); this.resetTokenService = resetTokenService; this.sessionService = sessionService; + this.settingService = settingService; + this.logger = config.getLogger('routes/user-controller.ts'); + this.unleashUrl = config.server.unleashUrl; this.get('/', this.getUsers, ADMIN); this.get('/search', this.search); @@ -69,8 +80,7 @@ export default class UserAdminController extends Controller { this.get('/active-sessions', this.getActiveSessions, ADMIN); } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - async resetPassword(req, res): Promise { + async resetPassword(req: IAuthRequest, res: Response): Promise { const { user } = req; const receiver = req.body.id; const resetPasswordUrl = @@ -78,24 +88,17 @@ export default class UserAdminController extends Controller { res.json({ resetPasswordUrl }); } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types async getUsers(req: Request, res: Response): Promise { - try { - const users = await this.userService.getAll(); - const rootRoles = await this.accessService.getRootRoles(); - const inviteLinks = - await this.resetTokenService.getActiveInvitations(); - - const usersWithInviteLinks = users.map((user) => { - const inviteLink = inviteLinks[user.id] || ''; - return { ...user, inviteLink }; - }); + const users = await this.userService.getAll(); + const rootRoles = await this.accessService.getRootRoles(); + const inviteLinks = await this.resetTokenService.getActiveInvitations(); - res.json({ users: usersWithInviteLinks, rootRoles }); - } catch (error) { - this.logger.error(error); - res.status(500).send({ msg: 'server errors' }); - } + const usersWithInviteLinks = users.map((user) => { + const inviteLink = inviteLinks[user.id] || ''; + return { ...user, inviteLink }; + }); + + res.json({ users: usersWithInviteLinks, rootRoles }); } async getActiveSessions(req: Request, res: Response): Promise { @@ -103,7 +106,6 @@ export default class UserAdminController extends Controller { res.json(sessions); } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types async search(req: Request, res: Response): Promise { const { q } = req.query as any; try { @@ -122,7 +124,6 @@ export default class UserAdminController extends Controller { res.json(user); } - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types async createUser( req: IAuthRequest, res: Response, @@ -140,10 +141,20 @@ export default class UserAdminController extends Controller { }, user, ); - const inviteLink = await this.resetTokenService.createNewUserUrl( - createdUser.id, - user.email, - ); + + const passwordAuthSettings = + await this.settingService.get( + simpleAuthKey, + ); + + let inviteLink: string; + if (!passwordAuthSettings?.disabled) { + const inviteUrl = await this.resetTokenService.createNewUserUrl( + createdUser.id, + user.email, + ); + inviteLink = inviteUrl.toString(); + } let emailSent = false; const emailConfigured = this.emailService.configured(); @@ -154,7 +165,8 @@ export default class UserAdminController extends Controller { await this.emailService.sendGettingStartedMail( createdUser.name, createdUser.email, - inviteLink.toString(), + this.unleashUrl, + inviteLink, ); emailSent = true; } catch (e) { @@ -171,7 +183,7 @@ export default class UserAdminController extends Controller { res.status(201).send({ ...createdUser, - inviteLink, + inviteLink: inviteLink || this.unleashUrl, emailSent, rootRole, }); @@ -227,5 +239,3 @@ export default class UserAdminController extends Controller { res.status(200).send(); } } - -module.exports = UserAdminController; diff --git a/src/lib/routes/auth/simple-password-provider.js b/src/lib/routes/auth/simple-password-provider.js deleted file mode 100644 index de86dc7bb5f..00000000000 --- a/src/lib/routes/auth/simple-password-provider.js +++ /dev/null @@ -1,27 +0,0 @@ -const Controller = require('../controller'); - -class PasswordProvider extends Controller { - constructor(config, { userService }) { - super(config); - this.logger = config.getLogger('/auth/password-provider.js'); - this.userService = userService; - - this.post('/login', this.login); - } - - async login(req, res) { - const { username, password } = req.body; - - if (!username || !password) { - return res.status(400).json({ - message: 'You must provide username and password', - }); - } - - const user = await this.userService.loginUser(username, password); - req.session.user = user; - return res.status(200).json(user); - } -} - -module.exports = PasswordProvider; diff --git a/src/lib/routes/auth/simple-password-provider.test.ts b/src/lib/routes/auth/simple-password-provider.test.ts index 73a3c6ddb52..0a438358202 100644 --- a/src/lib/routes/auth/simple-password-provider.test.ts +++ b/src/lib/routes/auth/simple-password-provider.test.ts @@ -9,6 +9,7 @@ test('Should require password', async () => { const app = express(); app.use(express.json()); const userService = () => {}; + // @ts-ignore const ctr = new PasswordProvider({ getLogger }, { userService }); //@ts-ignore @@ -41,6 +42,7 @@ test('Should login user', async () => { throw new Error('Wrong password'); }, }; + // @ts-ignore const ctr = new PasswordProvider({ getLogger }, { userService }); //@ts-ignore @@ -74,6 +76,7 @@ test('Should not login user with wrong password', async () => { throw new PasswordMismatchError(); }, }; + // @ts-ignore const ctr = new PasswordProvider({ getLogger }, { userService }); //@ts-ignore diff --git a/src/lib/routes/auth/simple-password-provider.ts b/src/lib/routes/auth/simple-password-provider.ts new file mode 100644 index 00000000000..4039b3504fa --- /dev/null +++ b/src/lib/routes/auth/simple-password-provider.ts @@ -0,0 +1,42 @@ +import { Response } from 'express'; +import { Logger } from '../../logger'; +import { IUnleashConfig } from '../../server-impl'; +import UserService from '../../services/user-service'; +import { IUnleashServices } from '../../types'; +import { NONE } from '../../types/permissions'; +import Controller from '../controller'; +import { IAuthRequest } from '../unleash-types'; + +class PasswordProvider extends Controller { + private userService: UserService; + + private logger: Logger; + + constructor( + config: IUnleashConfig, + { userService }: Pick, + ) { + super(config); + this.logger = config.getLogger('/auth/password-provider.js'); + this.userService = userService; + + this.post('/login', this.login, NONE); + } + + async login(req: IAuthRequest, res: Response): Promise { + const { username, password } = req.body; + + if (!username || !password) { + res.status(400).json({ + message: 'You must provide username and password', + }); + return; + } + + const user = await this.userService.loginUser(username, password); + req.session.user = user; + res.status(200).json(user); + } +} + +export default PasswordProvider; diff --git a/src/lib/routes/index.ts b/src/lib/routes/index.ts index 3d656693dd0..76961ef3151 100644 --- a/src/lib/routes/index.ts +++ b/src/lib/routes/index.ts @@ -1,6 +1,7 @@ import { Request, Response } from 'express'; import { BackstageController } from './backstage'; import ResetPasswordController from './auth/reset-password-controller'; +import SimplePasswordProvider from './auth/simple-password-provider'; import { IUnleashConfig } from '../types/option'; import { IUnleashServices } from '../types/services'; import { api } from './api-def'; @@ -10,7 +11,6 @@ const ClientApi = require('./client-api'); const Controller = require('./controller'); const HealthCheckController = require('./health-check'); const LogoutController = require('./logout'); -const SimplePasswordProvider = require('./auth/simple-password-provider'); class IndexRouter extends Controller { constructor(config: IUnleashConfig, services: IUnleashServices) { diff --git a/src/lib/services/email-service.ts b/src/lib/services/email-service.ts index 3d285b308d5..2b2bcb8b2bf 100644 --- a/src/lib/services/email-service.ts +++ b/src/lib/services/email-service.ts @@ -136,11 +136,12 @@ export class EmailService { async sendGettingStartedMail( name: string, recipient: string, - passwordLink: string, + unleashUrl: string, + passwordLink?: string, ): Promise { if (this.configured()) { const year = new Date().getFullYear(); - const context = { passwordLink, name, year }; + const context = { passwordLink, name, year, unleashUrl }; const bodyHtml = await this.compileTemplate( 'getting-started', TemplateFormat.HTML, diff --git a/src/mailtemplates/getting-started/getting-started.html.mustache b/src/mailtemplates/getting-started/getting-started.html.mustache index ff759b0480e..c06a40ab8fa 100644 --- a/src/mailtemplates/getting-started/getting-started.html.mustache +++ b/src/mailtemplates/getting-started/getting-started.html.mustache @@ -484,10 +484,18 @@

You have been invited to your organization's Unleash account - the new way of delivering software.

+ {{# passwordLink }}

Below is your magic sign-in link. Just click the button and follow the steps provided to update your password and get started using Unleash.


Setup account + {{/ passwordLink }} + {{^ passwordLink }} +

Use the link below to access your Unleash instance.

+ +
+
Login + {{/ passwordLink }}

By the way - did you know we have built Unleash on best practice when releasing new features. Our solution removes pressure from developers, allowing you to focus on delivering value through experimentation.

diff --git a/src/mailtemplates/getting-started/getting-started.plain.mustache b/src/mailtemplates/getting-started/getting-started.plain.mustache index e0c8f082f32..24b987a1958 100644 --- a/src/mailtemplates/getting-started/getting-started.plain.mustache +++ b/src/mailtemplates/getting-started/getting-started.plain.mustache @@ -2,9 +2,19 @@ Welcome to Unleash {{ name }}! You have been invited to your organization's Unleash account - the new way of delivering software. +{{# passwordLink }} + Below is your magic sign-in link. Just click the button and follow the steps provided to update your password and get started using Unleash. Visit {{{ passwordLink }}} to get started. +{{/ passwordLink }} +{{^ passwordLink }} + +Use the link below to access your Unleash instance: + +{{{ unleashUrl }}} + +{{/ passwordLink }} By the way - did you know we have built Unleash on best practice when releasing new features. Our solution removes pressure from developers, allowing you to focus on delivering value through experimentation. Looking forward to having a lot of fun together.