feat(oauth): Add an OAuth-sessionToken exchange endpoint
This commit is contained in:
Родитель
11beb47be4
Коммит
17dbac74cd
21
docs/api.md
21
docs/api.md
|
@ -24,6 +24,7 @@ see [`mozilla/fxa-js-client`](https://github.com/mozilla/fxa-js-client).
|
|||
* [Account](#account)
|
||||
* [POST /account/create](#post-accountcreate)
|
||||
* [POST /account/login](#post-accountlogin)
|
||||
* [GET /account/oauth_exchange (:lock: oauthToken)](#get-accountoauth_exchange)
|
||||
* [GET /account/status (:lock::unlock: sessionToken)](#get-accountstatus)
|
||||
* [POST /account/status](#post-accountstatus)
|
||||
* [GET /account/profile (:lock: sessionToken, oauthToken)](#get-accountprofile)
|
||||
|
@ -295,6 +296,8 @@ for `code` and `errno` are:
|
|||
This request requires two step authentication enabled on your account.
|
||||
* `code: 400, errno: 161`:
|
||||
Recovery key already exists.
|
||||
* `code: 401, errno: 162`:
|
||||
The required scope(s) for this request was not found.
|
||||
* `code: 503, errno: 201`:
|
||||
Service unavailable
|
||||
* `code: 503, errno: 202`:
|
||||
|
@ -327,6 +330,7 @@ include additional response properties:
|
|||
* `errno: 135`: bouncedAt
|
||||
* `errno: 152`
|
||||
* `errno: 153`
|
||||
* `errno: 162`: requiredScopes
|
||||
* `errno: 201`: retryAfter
|
||||
* `errno: 202`: retryAfter
|
||||
* `errno: 203`: service, operation
|
||||
|
@ -716,6 +720,23 @@ by the following errors
|
|||
This request requires two step authentication enabled on your account.
|
||||
|
||||
|
||||
#### GET /account/oauth_exchange
|
||||
|
||||
:lock: authenticated with OAuth bearer token
|
||||
<!--begin-route-get-accountoauth_exchange-->
|
||||
|
||||
<!--end-route-get-accountoauth_exchange-->
|
||||
|
||||
##### Error responses
|
||||
|
||||
Failing requests may be caused
|
||||
by the following errors
|
||||
(this is not an exhaustive list):
|
||||
|
||||
* `code: 401, errno: 162`:
|
||||
The required scope(s) for this request was not found.
|
||||
|
||||
|
||||
#### GET /account/status
|
||||
|
||||
:lock::unlock: Optionally HAWK-authenticated with session token
|
||||
|
|
13
lib/error.js
13
lib/error.js
|
@ -75,6 +75,8 @@ var ERRNO = {
|
|||
TOTP_REQUIRED: 160,
|
||||
RECOVERY_KEY_EXISTS: 161,
|
||||
|
||||
INVALID_SCOPE: 162,
|
||||
|
||||
SERVER_BUSY: 201,
|
||||
FEATURE_NOT_ENABLED: 202,
|
||||
BACKEND_SERVICE_FAILURE: 203,
|
||||
|
@ -851,6 +853,17 @@ AppError.backendServiceFailure = (service, operation) => {
|
|||
})
|
||||
}
|
||||
|
||||
AppError.invalidScope = (requiredScopes) => {
|
||||
return new AppError({
|
||||
code: 401,
|
||||
error: 'Unauthorized',
|
||||
errno: ERRNO.INVALID_SCOPE,
|
||||
message: 'The required scope(s) for this request was not found.'
|
||||
}, {
|
||||
requiredScopes
|
||||
})
|
||||
}
|
||||
|
||||
AppError.unexpectedError = () => {
|
||||
return new AppError({})
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ const MS_ONE_DAY = MS_ONE_HOUR * 24
|
|||
const MS_ONE_WEEK = MS_ONE_DAY * 7
|
||||
const MS_ONE_MONTH = MS_ONE_DAY * 30
|
||||
|
||||
const SCOPE_ADMIN = 'https://identity.mozilla.com/admin'
|
||||
|
||||
module.exports = (log, db, mailer, Password, config, customs, signinUtils, push) => {
|
||||
const tokenCodeConfig = config.signinConfirmation.tokenVerificationCode
|
||||
const tokenCodeLifetime = tokenCodeConfig && tokenCodeConfig.codeLifetime || MS_ONE_HOUR
|
||||
|
@ -726,6 +728,59 @@ module.exports = (log, db, mailer, Password, config, customs, signinUtils, push)
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/account/oauth_exchange',
|
||||
options: {
|
||||
auth: {
|
||||
strategy: 'oauthToken',
|
||||
payload: false, // no payload authentication in OAuth.
|
||||
},
|
||||
response: {
|
||||
schema: {
|
||||
sessionToken: isA.string().regex(HEX_STRING).required(),
|
||||
}
|
||||
}
|
||||
},
|
||||
handler: async function (request) {
|
||||
const auth = request.auth
|
||||
const uid = auth.credentials.user;
|
||||
const scope = ScopeSet.fromArray(auth.credentials.scope);
|
||||
if (! scope.contains(SCOPE_ADMIN)) {
|
||||
throw error.invalidScope(SCOPE_ADMIN)
|
||||
}
|
||||
|
||||
const account = await db.account(uid);
|
||||
const {
|
||||
browser: uaBrowser,
|
||||
browserVersion: uaBrowserVersion,
|
||||
os: uaOS,
|
||||
osVersion: uaOSVersion,
|
||||
deviceType: uaDeviceType,
|
||||
formFactor: uaFormFactor
|
||||
} = request.app.ua
|
||||
|
||||
const sessionToken = await db.createSessionToken({
|
||||
uid: account.uid,
|
||||
email: account.email,
|
||||
emailCode: account.emailCode,
|
||||
emailVerified: account.emailVerified,
|
||||
verifierSetAt: account.verifierSetAt,
|
||||
mustVerify: false,
|
||||
tokenVerificationCode: undefined,
|
||||
tokenVerificationCodeExpiresAt: undefined,
|
||||
tokenVerificationId: undefined,
|
||||
uaBrowser,
|
||||
uaBrowserVersion,
|
||||
uaOS,
|
||||
uaOSVersion,
|
||||
uaDeviceType,
|
||||
uaFormFactor
|
||||
})
|
||||
|
||||
return {sessionToken: sessionToken.data}
|
||||
}
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/account/status',
|
||||
|
|
Загрузка…
Ссылка в новой задаче