2022-12-22 18:41:19 +01:00
import {
jwt ,
key ,
fields ,
parameters ,
response ,
errorExamples ,
jsonResponse ,
uploadImg ,
token ,
} from './lib.mjs'
/ *
// Update email address
app . post (
'/account/change/email' ,
passport . authenticate ( ... jwt ) ,
( req , res ) => User . confirmChangedEmail ( req , res , params )
)
// Re-send account confirmation
app . post (
'/resend' ,
( req , res ) => User . resend ( req , res , params )
)
* /
const common = {
tags : [ 'Signup & Login' ] ,
security : [ jwt , key ] ,
}
const local = {
params : {
id : {
in : 'path' ,
name : 'id' ,
required : true ,
description : 'The confirmation ID' ,
schema : {
example : '3985f312-e407-458a-a78c-4596c361d284' ,
type : 'string' ,
} ,
} ,
} ,
password : {
description : ` The user's password ` ,
type : 'string' ,
example : 'super secret password here' ,
} ,
token : {
description : ` The one-time password token from the MFA app (optional) ` ,
type : 'number' ,
example : 230945 ,
} ,
mfa : {
description : ` Whether to enable MFA or not ` ,
type : 'boolean' ,
example : true ,
} ,
secret : {
description : 'The secret to generate one-time passwords with' ,
type : 'string' ,
example : 'KBTSKUKRDJPEGCZK' ,
} ,
}
// Paths
export const paths = { }
// Create account (signup)
paths [ '/signup' ] = {
post : {
tags : [ 'Signup & Login' ] ,
summary : 'Sign up for a FreeSewing account' ,
description :
'Creates a new inactive account. The account will require confirmation via a link sent to the email address that the user submitted.' ,
requestBody : {
required : true ,
content : {
'application/json' : {
schema : {
type : 'object' ,
properties : {
email : response . body . userAccount . properties . email ,
language : response . body . userAccount . properties . language ,
} ,
} ,
} ,
} ,
} ,
responses : {
201 : {
... response . status [ '201' ] ,
... jsonResponse ( {
result : fields . result ,
email : response . body . userAccount . properties . email ,
} ) ,
} ,
400 : {
... response . status [ '400' ] ,
description :
response . status [ '400' ] . description +
errorExamples ( [
'postBodyMissing' ,
'emailMissing' ,
'languageMissing' ,
'unsupportedLanguage' ,
] ) ,
} ,
500 : response . status [ '500' ] ,
} ,
} ,
}
// Confirm account
paths [ '/confirm/signup/{id}' ] = {
post : {
tags : [ 'Signup & Login' ] ,
parameters : [ local . params . id ] ,
summary : 'Confirm a FreeSewing account' ,
description : 'Confirmes a new inactive account.' ,
requestBody : {
required : true ,
content : {
'application/json' : {
schema : {
type : 'object' ,
properties : {
consent : response . body . userAccount . properties . consent ,
} ,
} ,
} ,
} ,
} ,
responses : {
200 : {
... response . status [ '200' ] ,
... jsonResponse ( {
result : fields . result ,
token ,
account : response . body . userAccount ,
} ) ,
} ,
400 : {
... response . status [ '400' ] ,
description :
response . status [ '400' ] . description +
errorExamples ( [ 'postBodyMissing' , 'consentRequired' ] ) ,
} ,
404 : response . status [ '404' ] ,
500 : response . status [ '500' ] ,
} ,
} ,
}
// Login
paths [ '/login' ] = {
post : {
tags : [ 'Signup & Login' ] ,
summary : 'Log in to a FreeSewing account' ,
description :
"Logs in to an existing and active account. If MFA is enabled, you must also send the `token`. <br>The `username` field used for the login can contain one the User's `username`, `email`, or `id`." ,
requestBody : {
required : true ,
content : {
'application/json' : {
schema : {
type : 'object' ,
properties : {
username : response . body . userAccount . properties . email ,
password : local . password ,
token : local . token ,
} ,
} ,
} ,
} ,
} ,
responses : {
200 : {
... response . status [ '200' ] ,
... jsonResponse ( {
result : fields . result ,
token ,
account : response . body . userAccount ,
} ) ,
} ,
400 : {
... response . status [ '400' ] ,
description :
response . status [ '400' ] . description +
errorExamples ( [ 'postBodyMissing' , 'usernameMissing' , 'passwordMissing' ] ) ,
} ,
401 : response . status [ '401' ] ,
500 : response . status [ '500' ] ,
} ,
} ,
}
// Load user account
paths [ '/account/{auth}' ] = {
get : {
... common ,
tags : [ 'Account' ] ,
summary : ` Get account data ` ,
description : 'Retrieves account info for the authenticated user' ,
parameters : [ parameters . auth ] ,
responses : {
200 : {
description :
'**Success - Account data returned**\n\n' +
'Status code `200` indicates that the resource was returned successfully.' ,
... jsonResponse ( {
result : {
... fields . result ,
example : 'success' ,
} ,
account : response . body . userAccount ,
} ) ,
} ,
401 : response . status [ '401' ] ,
403 : {
... response . status [ '403' ] ,
description :
response . status [ '403' ] . description +
errorExamples ( [ 'accountStatusLacking' , 'insufficientAccessLevel' ] ) ,
} ,
404 : response . status [ '404' ] ,
500 : response . status [ '500' ] ,
} ,
} ,
patch : {
... common ,
tags : [ 'Account' ] ,
summary : 'Update account data' ,
description : ` Updates information for the User's account. ` ,
parameters : [ parameters . auth ] ,
requestBody : {
required : true ,
content : {
'application/json' : {
schema : {
type : 'object' ,
properties : {
bio : response . body . userAccount . properties . bio ,
consent : response . body . userAccount . properties . consent ,
control : response . body . userAccount . properties . control ,
email : response . body . userAccount . properties . email ,
github : response . body . userAccount . properties . github ,
img : uploadImg ,
language : response . body . userAccount . properties . language ,
newsletter : response . body . userAccount . properties . newsletter ,
username : response . body . userAccount . properties . username ,
} ,
} ,
} ,
} ,
} ,
responses : {
200 : {
description :
'**Success - Account data updated**\n\n' +
'Status code `200` indicates that the resource was updated successfully.' ,
... jsonResponse ( {
result : {
... fields . result ,
example : 'success' ,
} ,
account : response . body . userAccount ,
} ) ,
} ,
401 : response . status [ '401' ] ,
403 : {
... response . status [ '403' ] ,
description :
response . status [ '403' ] . description +
errorExamples ( [ 'accountStatusLacking' , 'insufficientAccessLevel' ] ) ,
} ,
404 : response . status [ '404' ] ,
500 : response . status [ '500' ] ,
} ,
} ,
// Remove a User
delete : {
... common ,
tags : [ 'Account' ] ,
summary : 'Remove account' ,
description : 'Removes the account of the currently authenticated user.' ,
parameters : [ parameters . auth ] ,
responses : {
204 : response . status [ '204' ] ,
401 : response . status [ '401' ] ,
403 : {
... response . status [ '403' ] ,
description :
response . status [ '403' ] . description +
errorExamples ( [ 'accountStatusLacking' , 'insufficientAccessLevel' ] ) ,
} ,
404 : response . status [ '404' ] ,
500 : response . status [ '500' ] ,
} ,
} ,
}
// FIXME: These are not implemented yet
paths [ '/account/export/{auth} [FIXME]' ] = {
get : {
... common ,
tags : [ 'Account' ] ,
summary : ` Export account data ` ,
description : 'This endpoint is not yet available.' ,
parameters : [ parameters . auth ] ,
responses : { 404 : response . status [ '404' ] } ,
} ,
}
paths [ '/account/restrict/{auth} [FIXME]' ] = {
post : {
... common ,
tags : [ 'Account' ] ,
summary : ` Restrict processing of account data ` ,
description : 'This endpoint is not yet available.' ,
parameters : [ parameters . auth ] ,
responses : { 404 : response . status [ '404' ] } ,
} ,
}
paths [ '/account/recover [FIXME]' ] = {
post : {
tags : [ 'Account' ] ,
summary : ` Recover account access (reset password) ` ,
description : 'This endpoint is not yet available.' ,
responses : { 404 : response . status [ '404' ] } ,
} ,
}
const mfaCommon = {
... common ,
tags : [ 'Multi-Factor Authentication (MFA)' ] ,
}
// Enable MFA
paths [ '/account/mfa/{auth} [setup]' ] = {
post : {
... mfaCommon ,
summary : 'Setup Multi-Factor Authentication (MFA)' ,
description : ` This describes how to trigger the initial setup of MFA on the account ` ,
requestBody : {
required : true ,
content : {
'application/json' : {
schema : {
type : 'object' ,
properties : {
mfa : local . mfa ,
} ,
} ,
} ,
} ,
} ,
responses : {
200 : {
... response . status [ '200' ] ,
... jsonResponse ( {
result : fields . result ,
mfa : {
type : 'object' ,
properties : {
secret : local . secret ,
otpauth : {
description :
'The `otpauth://` URI to be consumed by an OTP app like Google Authenticator' ,
type : 'string' ,
example :
'otpauth://totp/FreeSewing:user-294?secret=KBTSKUKRDJPEGCZK&period=30&digits=6&algorithm=SHA1&issuer=FreeSewing' ,
} ,
qrcode : {
description :
'An SVG image that contains a QR code that resolves as the `otpauth` URI' ,
type : 'string' ,
example : '<svg xmlns="http://www.w3.org/2000/svg" ...truncated' ,
} ,
} ,
} ,
} ) ,
} ,
400 : {
... response . status [ '400' ] ,
description :
response . status [ '400' ] . description + errorExamples ( [ 'postBodyMissing' , 'mfaActive' ] ) ,
} ,
403 : response . status [ '403' ] ,
500 : response . status [ '500' ] ,
} ,
} ,
}
// Confirm MFA
paths [ '/account/mfa/{auth} [confirm]' ] = {
post : {
... mfaCommon ,
summary : 'Confirm Multi-Factor Authentication (MFA)' ,
description : ` This describes how to confirm the setup of MFA on the account ` ,
requestBody : {
required : true ,
content : {
'application/json' : {
schema : {
type : 'object' ,
properties : {
mfa : local . mfa ,
secret : local . secret ,
token : local . token ,
} ,
} ,
} ,
} ,
} ,
responses : {
200 : {
... response . status [ '200' ] ,
... jsonResponse ( { result : { ... fields . result , example : 'success' } } ) ,
} ,
400 : {
... response . status [ '400' ] ,
description :
response . status [ '400' ] . description + errorExamples ( [ 'postBodyMissing' , 'mfaActive' ] ) ,
} ,
401 : response . status [ '401' ] ,
403 : response . status [ '403' ] ,
500 : response . status [ '500' ] ,
} ,
} ,
}
// Disable MFA
paths [ '/account/mfa/{auth} [disable]' ] = {
post : {
... mfaCommon ,
summary : 'Disable Multi-Factor Authentication (MFA)' ,
description :
'This describes how to disable MFA on the account. It requires the `password` and OTP `token`' ,
requestBody : {
required : true ,
content : {
'application/json' : {
schema : {
type : 'object' ,
properties : {
mfa : {
... local . mfa ,
example : false ,
} ,
password : local . password ,
token : local . token ,
} ,
} ,
} ,
} ,
} ,
responses : {
200 : {
... response . status [ '200' ] ,
... jsonResponse ( { result : { ... fields . result , example : 'success' } } ) ,
} ,
400 : {
... response . status [ '400' ] ,
description :
response . status [ '400' ] . description +
errorExamples ( [ 'postBodyMissing' , 'mfaTokenMissing' , 'passwordMissing' ] ) ,
} ,
401 : response . status [ '401' ] ,
403 : response . status [ '403' ] ,
500 : response . status [ '500' ] ,
} ,
} ,
}
// Get current JWT
paths [ '/whoami/jwt' ] = {
get : {
... paths [ '/account/{auth}' ] . get ,
tags : [ 'Whoami' ] ,
security : [ jwt ] ,
parameters : [ ] ,
summary : 'Retrieve the account data for the JSON Web Token used in the request' ,
description : 'This is an alias for `GET /account/jwt`.' ,
} ,
}
// Load user profile
paths [ '/users/{username}' ] = {
get : {
... common ,
tags : [ 'Users' ] ,
summary : ` Load user profile data ` ,
description : 'Retrieves profile data for user `username`' ,
parameters : [ parameters . auth ] ,
responses : {
200 : {
description :
'**Success - Profile data returned**\n\n' +
'Status code `200` indicates that the resource was returned successfully.' ,
... jsonResponse ( {
result : {
... fields . result ,
example : 'success' ,
} ,
user : response . body . userProfile ,
} ) ,
} ,
401 : response . status [ '401' ] ,
403 : {
... response . status [ '403' ] ,
description :
response . status [ '403' ] . description +
errorExamples ( [ 'accountStatusLacking' , 'insufficientAccessLevel' ] ) ,
} ,
404 : response . status [ '404' ] ,
500 : response . status [ '500' ] ,
} ,
} ,
}
2022-12-24 14:42:16 +01:00
// Check whether a username is available
const checkUsername = "Little Miss Can't Be Wrong"
paths [ '/available/username' ] = {
post : {
tags : [ 'Users' ] ,
summary : ` Checks whether a username is available ` ,
description :
'This allows a background check to see whether a username is available during signup' ,
requestBody : {
required : true ,
content : {
'application/json' : {
schema : {
type : 'object' ,
properties : {
username : {
type : 'string' ,
description : 'The username to check for availability' ,
example : checkUsername ,
} ,
} ,
} ,
} ,
} ,
} ,
responses : {
200 : {
description :
'**Success - Username is NOT available**\n\n' +
'Status code `200` indicates that the username exists.' ,
... jsonResponse ( {
result : 'success' ,
username : checkUsername ,
available : false ,
} ) ,
} ,
401 : response . status [ '401' ] ,
403 : {
... response . status [ '403' ] ,
description :
response . status [ '403' ] . description +
errorExamples ( [ 'accountStatusLacking' , 'insufficientAccessLevel' ] ) ,
} ,
404 : response . status [ '404' ] ,
500 : response . status [ '500' ] ,
} ,
} ,
}