aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers/api/users/token.ts
blob: c6afea67c20058103c58ee375d027b0cd00a9bfd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import express from 'express'
import { logger } from '@server/helpers/logger'
import { CONFIG } from '@server/initializers/config'
import { OTP } from '@server/initializers/constants'
import { getAuthNameFromRefreshGrant, getBypassFromExternalAuth, getBypassFromPasswordGrant } from '@server/lib/auth/external-auth'
import { handleOAuthToken, MissingTwoFactorError } from '@server/lib/auth/oauth'
import { BypassLogin, revokeToken } from '@server/lib/auth/oauth-model'
import { Hooks } from '@server/lib/plugins/hooks'
import { asyncMiddleware, authenticate, buildRateLimiter, openapiOperationDoc } from '@server/middlewares'
import { buildUUID } from '@shared/extra-utils'
import { ScopedToken } from '@shared/models/users/user-scoped-token'

const tokensRouter = express.Router()

const loginRateLimiter = buildRateLimiter({
  windowMs: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS,
  max: CONFIG.RATES_LIMIT.LOGIN.MAX
})

tokensRouter.post('/token',
  loginRateLimiter,
  openapiOperationDoc({ operationId: 'getOAuthToken' }),
  asyncMiddleware(handleToken)
)

tokensRouter.post('/revoke-token',
  openapiOperationDoc({ operationId: 'revokeOAuthToken' }),
  authenticate,
  asyncMiddleware(handleTokenRevocation)
)

tokensRouter.get('/scoped-tokens',
  authenticate,
  getScopedTokens
)

tokensRouter.post('/scoped-tokens',
  authenticate,
  asyncMiddleware(renewScopedTokens)
)

// ---------------------------------------------------------------------------

export {
  tokensRouter
}
// ---------------------------------------------------------------------------

async function handleToken (req: express.Request, res: express.Response, next: express.NextFunction) {
  const grantType = req.body.grant_type

  try {
    const bypassLogin = await buildByPassLogin(req, grantType)

    const refreshTokenAuthName = grantType === 'refresh_token'
      ? await getAuthNameFromRefreshGrant(req.body.refresh_token)
      : undefined

    const options = {
      refreshTokenAuthName,
      bypassLogin
    }

    const token = await handleOAuthToken(req, options)

    res.set('Cache-Control', 'no-store')
    res.set('Pragma', 'no-cache')

    Hooks.runAction('action:api.user.oauth2-got-token', { username: token.user.username, ip: req.ip, req, res })

    return res.json({
      token_type: 'Bearer',

      access_token: token.accessToken,
      refresh_token: token.refreshToken,

      expires_in: token.accessTokenExpiresIn,
      refresh_token_expires_in: token.refreshTokenExpiresIn
    })
  } catch (err) {
    logger.warn('Login error', { err })

    if (err instanceof MissingTwoFactorError) {
      res.set(OTP.HEADER_NAME, OTP.HEADER_REQUIRED_VALUE)
    }

    return res.fail({
      status: err.code,
      message: err.message,
      type: err.name
    })
  }
}

async function handleTokenRevocation (req: express.Request, res: express.Response) {
  const token = res.locals.oauth.token

  const result = await revokeToken(token, { req, explicitLogout: true })

  return res.json(result)
}

function getScopedTokens (req: express.Request, res: express.Response) {
  const user = res.locals.oauth.token.user

  return res.json({
    feedToken: user.feedToken
  } as ScopedToken)
}

async function renewScopedTokens (req: express.Request, res: express.Response) {
  const user = res.locals.oauth.token.user

  user.feedToken = buildUUID()
  await user.save()

  return res.json({
    feedToken: user.feedToken
  } as ScopedToken)
}

async function buildByPassLogin (req: express.Request, grantType: string): Promise<BypassLogin> {
  if (grantType !== 'password') return undefined

  if (req.body.externalAuthToken) {
    // Consistency with the getBypassFromPasswordGrant promise
    return getBypassFromExternalAuth(req.body.username, req.body.externalAuthToken)
  }

  return getBypassFromPasswordGrant(req.body.username, req.body.password)
}