]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/lib/auth/oauth.ts
Move uuid stuff in extra utils
[github/Chocobozzz/PeerTube.git] / server / lib / auth / oauth.ts
CommitLineData
41fb13c3 1import express from 'express'
f43db2f4
C
2import {
3 InvalidClientError,
4 InvalidGrantError,
5 InvalidRequestError,
6 Request,
7 Response,
8 UnauthorizedClientError,
9 UnsupportedGrantTypeError
10} from 'oauth2-server'
06aad801 11import { randomBytesPromise } from '@server/helpers/core-utils'
f43db2f4 12import { MOAuthClient } from '@server/types/models'
f304a158 13import { sha1 } from '@shared/extra-utils'
f43db2f4
C
14import { OAUTH_LIFETIME } from '../../initializers/constants'
15import { BypassLogin, getClient, getRefreshToken, getUser, revokeToken, saveToken } from './oauth-model'
16
17/**
18 *
19 * Reimplement some functions of OAuth2Server to inject external auth methods
20 *
21 */
22
23const oAuthServer = new (require('oauth2-server'))({
24 accessTokenLifetime: OAUTH_LIFETIME.ACCESS_TOKEN,
25 refreshTokenLifetime: OAUTH_LIFETIME.REFRESH_TOKEN,
26
27 // See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications
28 model: require('./oauth-model')
29})
30
31// ---------------------------------------------------------------------------
32
33async function handleOAuthToken (req: express.Request, options: { refreshTokenAuthName?: string, bypassLogin?: BypassLogin }) {
34 const request = new Request(req)
35 const { refreshTokenAuthName, bypassLogin } = options
36
37 if (request.method !== 'POST') {
38 throw new InvalidRequestError('Invalid request: method must be POST')
39 }
40
41 if (!request.is([ 'application/x-www-form-urlencoded' ])) {
42 throw new InvalidRequestError('Invalid request: content must be application/x-www-form-urlencoded')
43 }
44
45 const clientId = request.body.client_id
46 const clientSecret = request.body.client_secret
47
48 if (!clientId || !clientSecret) {
49 throw new InvalidClientError('Invalid client: cannot retrieve client credentials')
50 }
51
52 const client = await getClient(clientId, clientSecret)
53 if (!client) {
54 throw new InvalidClientError('Invalid client: client is invalid')
55 }
56
57 const grantType = request.body.grant_type
58 if (!grantType) {
59 throw new InvalidRequestError('Missing parameter: `grant_type`')
60 }
61
62 if (![ 'password', 'refresh_token' ].includes(grantType)) {
63 throw new UnsupportedGrantTypeError('Unsupported grant type: `grant_type` is invalid')
64 }
65
66 if (!client.grants.includes(grantType)) {
67 throw new UnauthorizedClientError('Unauthorized client: `grant_type` is invalid')
68 }
69
70 if (grantType === 'password') {
71 return handlePasswordGrant({
72 request,
73 client,
74 bypassLogin
75 })
76 }
77
78 return handleRefreshGrant({
79 request,
80 client,
81 refreshTokenAuthName
82 })
83}
84
98ab5dc8 85function handleOAuthAuthenticate (
f43db2f4
C
86 req: express.Request,
87 res: express.Response,
88 authenticateInQuery = false
89) {
90 const options = authenticateInQuery
91 ? { allowBearerTokensInQueryString: true }
92 : {}
93
94 return oAuthServer.authenticate(new Request(req), new Response(res), options)
95}
96
97export {
98 handleOAuthToken,
99 handleOAuthAuthenticate
100}
101
102// ---------------------------------------------------------------------------
103
104async function handlePasswordGrant (options: {
105 request: Request
106 client: MOAuthClient
107 bypassLogin?: BypassLogin
108}) {
109 const { request, client, bypassLogin } = options
110
111 if (!request.body.username) {
112 throw new InvalidRequestError('Missing parameter: `username`')
113 }
114
115 if (!bypassLogin && !request.body.password) {
116 throw new InvalidRequestError('Missing parameter: `password`')
117 }
118
119 const user = await getUser(request.body.username, request.body.password, bypassLogin)
120 if (!user) throw new InvalidGrantError('Invalid grant: user credentials are invalid')
121
122 const token = await buildToken()
123
124 return saveToken(token, client, user, { bypassLogin })
125}
126
127async function handleRefreshGrant (options: {
128 request: Request
129 client: MOAuthClient
130 refreshTokenAuthName: string
131}) {
132 const { request, client, refreshTokenAuthName } = options
133
134 if (!request.body.refresh_token) {
135 throw new InvalidRequestError('Missing parameter: `refresh_token`')
136 }
137
138 const refreshToken = await getRefreshToken(request.body.refresh_token)
139
140 if (!refreshToken) {
141 throw new InvalidGrantError('Invalid grant: refresh token is invalid')
142 }
143
144 if (refreshToken.client.id !== client.id) {
145 throw new InvalidGrantError('Invalid grant: refresh token is invalid')
146 }
147
148 if (refreshToken.refreshTokenExpiresAt && refreshToken.refreshTokenExpiresAt < new Date()) {
149 throw new InvalidGrantError('Invalid grant: refresh token has expired')
150 }
151
152 await revokeToken({ refreshToken: refreshToken.refreshToken })
153
154 const token = await buildToken()
155
156 return saveToken(token, client, refreshToken.user, { refreshTokenAuthName })
157}
158
159function generateRandomToken () {
160 return randomBytesPromise(256)
161 .then(buffer => sha1(buffer))
162}
163
164function getTokenExpiresAt (type: 'access' | 'refresh') {
165 const lifetime = type === 'access'
166 ? OAUTH_LIFETIME.ACCESS_TOKEN
167 : OAUTH_LIFETIME.REFRESH_TOKEN
168
169 return new Date(Date.now() + lifetime * 1000)
170}
171
172async function buildToken () {
173 const [ accessToken, refreshToken ] = await Promise.all([ generateRandomToken(), generateRandomToken() ])
174
175 return {
176 accessToken,
177 refreshToken,
178 accessTokenExpiresAt: getTokenExpiresAt('access'),
179 refreshTokenExpiresAt: getTokenExpiresAt('refresh')
180 }
181}