Skip to content

Commit

Permalink
feat(auth): implement SAML SLO (#11599)
Browse files Browse the repository at this point in the history
* feat(auth): implement SAML SLO

* use storage class

* update pro

* bump pro

* fix e2e tests
  • Loading branch information
laurentlp committed Mar 17, 2022
1 parent bcaefe5 commit 368addd
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 16 deletions.
2 changes: 1 addition & 1 deletion packages/bp/e2e/admin/logout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('Admin - Logout', () => {
await clickOn('#btn-menu-user-dropdown')
await clickOn('#btn-logout')

const response = await getResponse('/api/v2/admin/auth/logout', 'POST')
const response = await getResponse('/api/v2/admin/auth/logout', 'GET')
const headers = response.request().headers()

let profileStatus: number
Expand Down
5 changes: 3 additions & 2 deletions packages/bp/e2e/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ export const logout = async () => {
await clickOn('#btn-menu-user-dropdown')
await clickOn('#btn-logout')

const response = await getResponse('/api/v2/admin/auth/logout', 'POST')
expect(response.status()).toBe(200)
const response = await getResponse('/api/v2/admin/auth/logout', 'GET')
expect(response.status()).toBeGreaterThanOrEqual(200)
expect(response.status()).toBeLessThan(400)

await page.waitForNavigation()
}
Expand Down
4 changes: 2 additions & 2 deletions packages/bp/src/admin/auth/auth-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ class AuthRouter extends CustomAdminRouter {
})
)

router.post(
router.get(
'/logout',
this.checkTokenHeader,
this.asyncMiddleware(async (req: RequestWithUser, res) => {
await this.authService.invalidateToken(req.tokenUser!)
res.sendStatus(200)
return this.authService.logout(req.tokenUser!.strategy, req, res)
})
)

Expand Down
4 changes: 3 additions & 1 deletion packages/bp/src/common/typings.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BotDetails, Flow, FlowNode, IO, RolloutStrategy, StageRequestApprovers, StrategyUser } from 'botpress/sdk'
import { Request } from 'express'
import { Request, Response } from 'express'
import { BotpressConfig } from '../core/config/botpress.config'
import { LicenseInfo, LicenseStatus } from './licensing-service'

Expand Down Expand Up @@ -92,6 +92,8 @@ export type RequestWithUser = Request & {
workspace?: string
}

export type LogoutCallback = (strategy: string, req: RequestWithUser, res: Response) => Promise<void>

export interface Bot {
id: string
name: string
Expand Down
27 changes: 25 additions & 2 deletions packages/bp/src/core/security/auth-service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { Logger, StrategyUser } from 'botpress/sdk'
import { JWT_COOKIE_NAME } from 'common/auth'
import { AuthPayload, AuthStrategyConfig, ChatUserAuth, TokenUser, TokenResponse } from 'common/typings'
import {
AuthPayload,
AuthStrategyConfig,
ChatUserAuth,
TokenUser,
TokenResponse,
RequestWithUser,
LogoutCallback
} from 'common/typings'
import { TYPES } from 'core/app/types'
import { AuthStrategy, ConfigProvider } from 'core/config'
import Database from 'core/database'
Expand Down Expand Up @@ -28,14 +36,15 @@ export const EXTERNAL_AUTH_HEADER = 'x-bp-externalauth'
export const SERVER_USER = 'server::modules'
const DEFAULT_CHAT_USER_AUTH_DURATION = '24h'

const getUserKey = (email, strategy) => `${email}_${strategy}`
const getUserKey = (email: string, strategy: string) => `${email}_${strategy}`

@injectable()
export class AuthService {
public strategyBasic!: StrategyBasic
private tokenVersions: Dic<number> = {}
private broadcastTokenChange: Function = this.local__tokenVersionChange
public jobService!: JobService
private logoutCallbacks: { [strategy: string]: LogoutCallback } = {}

constructor(
@inject(TYPES.Logger)
Expand Down Expand Up @@ -369,6 +378,20 @@ export class AuthService {
res.cookie(JWT_COOKIE_NAME, token.jwt, { maxAge: token.exp, httpOnly: true, ...cookieOptions })
return true
}

public addLogoutCallback(strategy: string, callback: LogoutCallback) {
this.logoutCallbacks[strategy] = callback
}

public async logout(strategy: string, req: RequestWithUser, res: Response) {
const callback = this.logoutCallbacks[strategy]

if (!callback) {
return res.sendStatus(200)
}

return callback(strategy, req, res)
}
}

export default AuthService
26 changes: 18 additions & 8 deletions packages/ui-shared-lite/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,24 @@ export const tokenNeedsRefresh = () => {
}

export const logout = async (getAxiosClient: () => AxiosInstance) => {
await getAxiosClient()
.post('/admin/auth/logout')
.catch(() => {})

// Clear access token and ID token from local storage
localStorage.removeItem(TOKEN_KEY)
// need to force reload otherwise the token wont clear properly
window.location.href = window.location.origin + window['ROOT_PATH']
let url = ''
try {
const resp = await getAxiosClient().get('/admin/auth/logout')

url = resp.data.url
} catch {
// Silently fails
} finally {
storage.del(TOKEN_KEY)

if (url) {
// If /logout gave us a URL, manually redirect to this URL
window.location.replace(url)
} else {
// need to force reload otherwise the token wont clear properly
window.location.href = window.location.origin + window['ROOT_PATH']
}
}
}

export const setVisitorId = (userId: string, userIdScope?: string) => {
Expand Down

0 comments on commit 368addd

Please sign in to comment.