peertube-plugin-livechat/server/lib/routers/api/auth.ts
John Livingston 90afdafbd9
Authentication token generation WIP (#98)
You can now generate links to join chatrooms with your current user. This can be used to create Docks in OBS for example. This could also be used to generate authentication token to join the chat from 3rd party tools.
2024-06-17 11:43:59 +02:00

190 lines
6.0 KiB
TypeScript

// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
//
// SPDX-License-Identifier: AGPL-3.0-only
import type { RegisterServerOptions } from '@peertube/peertube-types'
import type { Router, Request, Response, NextFunction } from 'express'
import { asyncMiddleware } from '../../middlewares/async'
import { getProsodyDomain } from '../../prosody/config/domain'
import { LivechatProsodyAuth } from '../../prosody/auth'
import { ExternalAuthOIDC } from '../../external-auth/oidc'
/**
* Instanciate the authentication API.
* This API is used by the frontend to get current user's XMPP credentials.
* @param options server register options
*/
async function initAuthApiRouter (options: RegisterServerOptions, router: Router): Promise<void> {
router.get('/auth', asyncMiddleware(
async (req: Request, res: Response, _next: NextFunction) => {
const user = await options.peertubeHelpers.user.getAuthUser(res)
if (!user) {
// No Peertube user, but perhaps an external authentication?
const token = req.header('X-Peertube-Plugin-Livechat-External-Auth-OIDC-Token')
if (token) {
try {
const oidc = ExternalAuthOIDC.singletonForToken(token)
if (oidc && await oidc.isOk()) {
const unserializedToken = await oidc.unserializeToken(token)
if (unserializedToken) {
res.status(200).json({
jid: unserializedToken.jid,
password: unserializedToken.password,
nickname: unserializedToken.nickname,
type: 'oidc'
})
return
}
}
} catch (err) {
options.peertubeHelpers.logger.error(err)
// Just continue with the normal flow.
}
}
}
const tempPassword = await LivechatProsodyAuth.singleton().getUserTempPassword(user)
if (!tempPassword) {
res.sendStatus(403)
return
}
res.status(200).json(tempPassword)
}
))
router.get('/auth/tokens', asyncMiddleware(
async (req: Request, res: Response, _next: NextFunction) => {
const user = await options.peertubeHelpers.user.getAuthUser(res)
try {
const tokens = await LivechatProsodyAuth.singleton().getUserTokens(user)
if (!tokens) {
res.sendStatus(403)
return
}
res.status(200).json(tokens)
} catch (err) {
options.peertubeHelpers.logger.error(err as string)
res.sendStatus(500)
}
}
))
router.post('/auth/tokens', asyncMiddleware(
async (req: Request, res: Response, _next: NextFunction) => {
const user = await options.peertubeHelpers.user.getAuthUser(res)
try {
const label = req.body.label
if ((typeof label !== 'string') || !label) {
res.sendStatus(400)
return
}
const token = await LivechatProsodyAuth.singleton().createUserToken(user, label)
if (!token) {
res.sendStatus(403)
return
}
res.status(200).json(token)
} catch (err) {
options.peertubeHelpers.logger.error(err as string)
res.sendStatus(500)
}
}
))
router.delete('/auth/tokens/:tokenId', asyncMiddleware(
async (req: Request, res: Response, _next: NextFunction) => {
const user = await options.peertubeHelpers.user.getAuthUser(res)
try {
const tokenId = parseInt(req.params.tokenId)
if (isNaN(tokenId)) {
res.sendStatus(400)
return
}
const r = await LivechatProsodyAuth.singleton().revokeUserToken(user, tokenId)
if (!r) {
res.sendStatus(403)
return
}
res.status(200).json(true)
} catch (err) {
options.peertubeHelpers.logger.error(err as string)
res.sendStatus(500)
}
}
))
}
/**
* Instanciates API used by the Prosody module http_auth.
* This is used to check user's credentials.
* @param options server register options
* @returns a router
*/
async function initUserAuthApiRouter (options: RegisterServerOptions, router: Router): Promise<void> {
const logger = options.peertubeHelpers.logger
router.post('/user/register', asyncMiddleware(
async (req: Request, res: Response, _next: NextFunction) => {
res.sendStatus(501)
}
))
router.get('/user/check_password', asyncMiddleware(
async (req: Request, res: Response, _next: NextFunction) => {
const prosodyDomain = await getProsodyDomain(options)
const user = req.query.user
const server = req.query.server
const pass = req.query.pass
if (server !== prosodyDomain) {
logger.warn(`Cannot call check_password on user on server ${server as string}.`)
res.status(200).send('false')
return
}
if (user && pass && await LivechatProsodyAuth.singleton().checkUserPassword(user as string, pass as string)) {
res.status(200).send('true')
return
}
res.status(200).send('false')
}
))
router.get('/user/user_exists', asyncMiddleware(
async (req: Request, res: Response, _next: NextFunction) => {
const prosodyDomain = await getProsodyDomain(options)
const user = req.query.user
const server = req.query.server
if (server !== prosodyDomain) {
logger.warn(`Cannot call user_exists on user on server ${server as string}.`)
res.status(200).send('false')
return
}
if (user && await LivechatProsodyAuth.singleton().userRegistered(user as string)) {
res.status(200).send('true')
return
}
res.status(200).send('false')
}
))
router.post('/user/set_password', asyncMiddleware(
async (req: Request, res: Response, _next: NextFunction) => {
res.sendStatus(501)
}
))
router.post('/user/remove_user', asyncMiddleware(
async (req: Request, res: Response, _next: NextFunction) => {
res.sendStatus(501)
}
))
}
export {
initAuthApiRouter,
initUserAuthApiRouter
}