Merge branch 'artonge/PeerTube-feature/logoutUrlForAuthProviders' into 'develop'

Artonge/peer tube feature/logout url for auth providers

See merge request framasoft/peertube/PeerTube!33
This commit is contained in:
Chocobozzz 2020-11-20 15:36:44 +01:00
commit db7510d632
8 changed files with 129 additions and 15 deletions

View File

@ -167,9 +167,13 @@ Ensure you have correctly configured PeerTube (config/ directory), in particular
const authHeaderValue = this.getRequestHeaderValue() const authHeaderValue = this.getRequestHeaderValue()
const headers = new HttpHeaders().set('Authorization', authHeaderValue) const headers = new HttpHeaders().set('Authorization', authHeaderValue)
this.http.post<void>(AuthService.BASE_REVOKE_TOKEN_URL, {}, { headers }) this.http.post<{ redirectUrl?: string }>(AuthService.BASE_REVOKE_TOKEN_URL, {}, { headers })
.subscribe( .subscribe(
() => { /* nothing to do */ }, res => {
if (res.redirectUrl) {
window.location.href = res.redirectUrl
}
},
err => console.error(err) err => console.error(err)
) )

View File

@ -52,7 +52,7 @@ async function handleTokenRevocation (req: express.Request, res: express.Respons
const token = res.locals.oauth.token const token = res.locals.oauth.token
res.locals.explicitLogout = true res.locals.explicitLogout = true
await revokeToken(token) const result = await revokeToken(token)
// FIXME: uncomment when https://github.com/oauthjs/node-oauth2-server/pull/289 is released // FIXME: uncomment when https://github.com/oauthjs/node-oauth2-server/pull/289 is released
// oAuthServer.revoke(req, res, err => { // oAuthServer.revoke(req, res, err => {
@ -68,7 +68,7 @@ async function handleTokenRevocation (req: express.Request, res: express.Respons
// } // }
// }) // })
return res.json() return res.json(result)
} }
async function onExternalUserAuthenticated (options: { async function onExternalUserAuthenticated (options: {

View File

@ -141,13 +141,15 @@ async function getUser (usernameOrEmail?: string, password?: string) {
return user return user
} }
async function revokeToken (tokenInfo: { refreshToken: string }) { async function revokeToken (tokenInfo: { refreshToken: string }): Promise<{ success: boolean, redirectUrl?: string }> {
const res: express.Response = this.request.res const res: express.Response = this.request.res
const token = await OAuthTokenModel.getByRefreshTokenAndPopulateUser(tokenInfo.refreshToken) const token = await OAuthTokenModel.getByRefreshTokenAndPopulateUser(tokenInfo.refreshToken)
if (token) { if (token) {
let redirectUrl: string
if (res.locals.explicitLogout === true && token.User.pluginAuth && token.authName) { if (res.locals.explicitLogout === true && token.User.pluginAuth && token.authName) {
PluginManager.Instance.onLogout(token.User.pluginAuth, token.authName, token.User) redirectUrl = await PluginManager.Instance.onLogout(token.User.pluginAuth, token.authName, token.User, this.request)
} }
clearCacheByToken(token.accessToken) clearCacheByToken(token.accessToken)
@ -155,10 +157,10 @@ async function revokeToken (tokenInfo: { refreshToken: string }) {
token.destroy() token.destroy()
.catch(err => logger.error('Cannot destroy token when revoking token.', { err })) .catch(err => logger.error('Cannot destroy token when revoking token.', { err }))
return true return { success: true, redirectUrl }
} }
return false return { success: false }
} }
async function saveToken (token: TokenInfo, client: OAuthClientModel, user: UserModel) { async function saveToken (token: TokenInfo, client: OAuthClientModel, user: UserModel) {

View File

@ -1,3 +1,4 @@
import * as express from 'express'
import { createReadStream, createWriteStream } from 'fs' import { createReadStream, createWriteStream } from 'fs'
import { outputFile, readJSON } from 'fs-extra' import { outputFile, readJSON } from 'fs-extra'
import { basename, join } from 'path' import { basename, join } from 'path'
@ -166,18 +167,25 @@ export class PluginManager implements ServerHook {
// ###################### External events ###################### // ###################### External events ######################
onLogout (npmName: string, authName: string, user: MUser) { async onLogout (npmName: string, authName: string, user: MUser, req: express.Request) {
const auth = this.getAuth(npmName, authName) const auth = this.getAuth(npmName, authName)
if (auth?.onLogout) { if (auth?.onLogout) {
logger.info('Running onLogout function from auth %s of plugin %s', authName, npmName) logger.info('Running onLogout function from auth %s of plugin %s', authName, npmName)
try { try {
auth.onLogout(user) // Force await, in case or onLogout returns a promise
const result = await auth.onLogout(user, req)
return typeof result === 'string'
? result
: undefined
} catch (err) { } catch (err) {
logger.warn('Cannot run onLogout function from auth %s of plugin %s.', authName, npmName, { err }) logger.warn('Cannot run onLogout function from auth %s of plugin %s.', authName, npmName, { err })
} }
} }
return undefined
} }
onSettingsChanged (name: string, settings: any) { onSettingsChanged (name: string, settings: any) {

View File

@ -0,0 +1,53 @@
async function register ({
registerExternalAuth,
peertubeHelpers
}) {
{
const result = registerExternalAuth({
authName: 'external-auth-7',
authDisplayName: () => 'External Auth 7',
onAuthRequest: (req, res) => {
result.userAuthenticated({
req,
res,
username: 'cid',
email: 'cid@example.com',
displayName: 'Cid Marquez'
})
},
onLogout: (user, req) => {
return 'https://example.com/redirectUrl'
}
})
}
{
const result = registerExternalAuth({
authName: 'external-auth-8',
authDisplayName: () => 'External Auth 8',
onAuthRequest: (req, res) => {
result.userAuthenticated({
req,
res,
username: 'cid',
email: 'cid@example.com',
displayName: 'Cid Marquez'
})
},
onLogout: (user, req) => {
return 'https://example.com/redirectUrl?access_token=' + req.headers['authorization'].split(' ')[1]
}
})
}
}
async function unregister () {
}
module.exports = {
register,
unregister
}
// ###########################################################################

View File

@ -0,0 +1,20 @@
{
"name": "peertube-plugin-test-external-auth-three",
"version": "0.0.1",
"description": "External auth three",
"engine": {
"peertube": ">=1.3.0"
},
"keywords": [
"peertube",
"plugin"
],
"homepage": "https://github.com/Chocobozzz/PeerTube",
"author": "Chocobozzz",
"bugs": "https://github.com/Chocobozzz/PeerTube/issues",
"library": "./main.js",
"staticDirs": {},
"css": [],
"clientScripts": [],
"translations": {}
}

View File

@ -73,7 +73,7 @@ describe('Test external auth plugins', function () {
server = await flushAndRunServer(1) server = await flushAndRunServer(1)
await setAccessTokensToServers([ server ]) await setAccessTokensToServers([ server ])
for (const suffix of [ 'one', 'two' ]) { for (const suffix of [ 'one', 'two', 'three' ]) {
await installPlugin({ await installPlugin({
url: server.url, url: server.url,
accessToken: server.accessToken, accessToken: server.accessToken,
@ -88,7 +88,7 @@ describe('Test external auth plugins', function () {
const config: ServerConfig = res.body const config: ServerConfig = res.body
const auths = config.plugin.registeredExternalAuths const auths = config.plugin.registeredExternalAuths
expect(auths).to.have.lengthOf(6) expect(auths).to.have.lengthOf(8)
const auth2 = auths.find((a) => a.authName === 'external-auth-2') const auth2 = auths.find((a) => a.authName === 'external-auth-2')
expect(auth2).to.exist expect(auth2).to.exist
@ -301,7 +301,7 @@ describe('Test external auth plugins', function () {
const config: ServerConfig = res.body const config: ServerConfig = res.body
const auths = config.plugin.registeredExternalAuths const auths = config.plugin.registeredExternalAuths
expect(auths).to.have.lengthOf(5) expect(auths).to.have.lengthOf(7)
const auth1 = auths.find(a => a.authName === 'external-auth-2') const auth1 = auths.find(a => a.authName === 'external-auth-2')
expect(auth1).to.not.exist expect(auth1).to.not.exist
@ -371,7 +371,7 @@ describe('Test external auth plugins', function () {
const config: ServerConfig = res.body const config: ServerConfig = res.body
const auths = config.plugin.registeredExternalAuths const auths = config.plugin.registeredExternalAuths
expect(auths).to.have.lengthOf(4) expect(auths).to.have.lengthOf(6)
const auth2 = auths.find((a) => a.authName === 'external-auth-2') const auth2 = auths.find((a) => a.authName === 'external-auth-2')
expect(auth2).to.not.exist expect(auth2).to.not.exist
@ -380,4 +380,30 @@ describe('Test external auth plugins', function () {
after(async function () { after(async function () {
await cleanupTests([ server ]) await cleanupTests([ server ])
}) })
it('Should forward the redirectUrl if the plugin returns one', async function () {
const resLogin = await loginExternal({
server,
npmName: 'test-external-auth-three',
authName: 'external-auth-7',
username: 'cid'
})
const resLogout = await logout(server.url, resLogin.access_token)
expect(resLogout.body.redirectUrl).to.equal('https://example.com/redirectUrl')
})
it('Should call the plugin\'s onLogout method with the request', async function () {
const resLogin = await loginExternal({
server,
npmName: 'test-external-auth-three',
authName: 'external-auth-8',
username: 'cid'
})
const resLogout = await logout(server.url, resLogin.access_token)
expect(resLogout.body.redirectUrl).to.equal('https://example.com/redirectUrl?access_token=' + resLogin.access_token)
})
}) })

View File

@ -21,7 +21,8 @@ interface RegisterServerAuthBase {
authName: string authName: string
// Called by PeerTube when a user from your plugin logged out // Called by PeerTube when a user from your plugin logged out
onLogout?(user: MUser): void // Returns a redirectUrl sent to the client or nothing
onLogout?(user: MUser, req: express.Request): Promise<string>
// Your plugin can hook PeerTube access/refresh token validity // Your plugin can hook PeerTube access/refresh token validity
// So you can control for your plugin the user session lifetime // So you can control for your plugin the user session lifetime