mirror of
https://github.com/sasjs/server.git
synced 2025-12-10 19:34:34 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a131adbae7 | ||
|
|
a20c3b9719 | ||
|
|
eee3a7b084 | ||
|
|
9c3da56901 | ||
|
|
7e6524d7e4 | ||
|
|
0ea2690616 | ||
|
|
b369759f0f | ||
|
|
ac9a835c5a | ||
|
|
e290751c87 |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,3 +1,17 @@
|
|||||||
|
# [0.9.0](https://github.com/sasjs/server/compare/v0.8.3...v0.9.0) (2022-07-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* removed secrets from env variables ([9c3da56](https://github.com/sasjs/server/commit/9c3da56901672a818f54267f9defc9f4701ab7fb))
|
||||||
|
|
||||||
|
## [0.8.3](https://github.com/sasjs/server/compare/v0.8.2...v0.8.3) (2022-07-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **deploy:** extract first json from zip file ([e290751](https://github.com/sasjs/server/commit/e290751c872d24009482871a8c398e834357dcde))
|
||||||
|
|
||||||
## [0.8.2](https://github.com/sasjs/server/compare/v0.8.1...v0.8.2) (2022-06-22)
|
## [0.8.2](https://github.com/sasjs/server/compare/v0.8.1...v0.8.2) (2022-06-22)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -105,10 +105,6 @@ CERT_CHAIN=certificate.pem (required)
|
|||||||
CA_ROOT=fullchain.pem (optional)
|
CA_ROOT=fullchain.pem (optional)
|
||||||
|
|
||||||
# ENV variables required for MODE: `server`
|
# ENV variables required for MODE: `server`
|
||||||
ACCESS_TOKEN_SECRET=<secret>
|
|
||||||
REFRESH_TOKEN_SECRET=<secret>
|
|
||||||
AUTH_CODE_SECRET=<secret>
|
|
||||||
SESSION_SECRET=<secret>
|
|
||||||
DB_CONNECT=mongodb+srv://<DB_USERNAME>:<DB_PASSWORD>@<CLUSTER>/<DB_NAME>?retryWrites=true&w=majority
|
DB_CONNECT=mongodb+srv://<DB_USERNAME>:<DB_PASSWORD>@<CLUSTER>/<DB_NAME>?retryWrites=true&w=majority
|
||||||
|
|
||||||
# options: [disable|enable] default: `disable` for `server` & `enable` for `desktop`
|
# options: [disable|enable] default: `disable` for `server` & `enable` for `desktop`
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ PORT=[5000] default value is 5000
|
|||||||
HELMET_CSP_CONFIG_PATH=./csp.config.json if omitted HELMET default will be used
|
HELMET_CSP_CONFIG_PATH=./csp.config.json if omitted HELMET default will be used
|
||||||
HELMET_COEP=[true|false] if omitted HELMET default will be used
|
HELMET_COEP=[true|false] if omitted HELMET default will be used
|
||||||
|
|
||||||
ACCESS_TOKEN_SECRET=<secret>
|
|
||||||
REFRESH_TOKEN_SECRET=<secret>
|
|
||||||
AUTH_CODE_SECRET=<secret>
|
|
||||||
SESSION_SECRET=<secret>
|
|
||||||
DB_CONNECT=mongodb+srv://<DB_USERNAME>:<DB_PASSWORD>@<CLUSTER>/<DB_NAME>?retryWrites=true&w=majority
|
DB_CONNECT=mongodb+srv://<DB_USERNAME>:<DB_PASSWORD>@<CLUSTER>/<DB_NAME>?retryWrites=true&w=majority
|
||||||
|
|
||||||
RUN_TIMES=[sas|js|sas,js|js,sas] default considered as sas
|
RUN_TIMES=[sas|js|sas,js|js,sas] default considered as sas
|
||||||
|
|||||||
@@ -47,41 +47,6 @@ components:
|
|||||||
- userId
|
- userId
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
LoginPayload:
|
|
||||||
properties:
|
|
||||||
username:
|
|
||||||
type: string
|
|
||||||
description: 'Username for user'
|
|
||||||
example: secretuser
|
|
||||||
password:
|
|
||||||
type: string
|
|
||||||
description: 'Password for user'
|
|
||||||
example: secretpassword
|
|
||||||
required:
|
|
||||||
- username
|
|
||||||
- password
|
|
||||||
type: object
|
|
||||||
additionalProperties: false
|
|
||||||
AuthorizeResponse:
|
|
||||||
properties:
|
|
||||||
code:
|
|
||||||
type: string
|
|
||||||
description: 'Authorization code'
|
|
||||||
example: someRandomCryptoString
|
|
||||||
required:
|
|
||||||
- code
|
|
||||||
type: object
|
|
||||||
additionalProperties: false
|
|
||||||
AuthorizePayload:
|
|
||||||
properties:
|
|
||||||
clientId:
|
|
||||||
type: string
|
|
||||||
description: 'Client ID'
|
|
||||||
example: clientID1
|
|
||||||
required:
|
|
||||||
- clientId
|
|
||||||
type: object
|
|
||||||
additionalProperties: false
|
|
||||||
ClientPayload:
|
ClientPayload:
|
||||||
properties:
|
properties:
|
||||||
clientId:
|
clientId:
|
||||||
@@ -440,13 +405,13 @@ components:
|
|||||||
type: object
|
type: object
|
||||||
Pick__LeanDocument_T_.Exclude_keyof_LeanDocument_T_.Exclude_keyofDocument._id-or-id-or-__v_-or-%24isSingleNested__:
|
Pick__LeanDocument_T_.Exclude_keyof_LeanDocument_T_.Exclude_keyofDocument._id-or-id-or-__v_-or-%24isSingleNested__:
|
||||||
properties:
|
properties:
|
||||||
id:
|
|
||||||
description: 'The string version of this documents _id.'
|
|
||||||
_id:
|
_id:
|
||||||
$ref: '#/components/schemas/_LeanDocument__LeanDocument_T__'
|
$ref: '#/components/schemas/_LeanDocument__LeanDocument_T__'
|
||||||
description: 'This documents _id.'
|
description: 'This documents _id.'
|
||||||
__v:
|
__v:
|
||||||
description: 'This documents __v.'
|
description: 'This documents __v.'
|
||||||
|
id:
|
||||||
|
description: 'The string version of this documents _id.'
|
||||||
type: object
|
type: object
|
||||||
description: 'From T, pick a set of properties whose keys are in the union K'
|
description: 'From T, pick a set of properties whose keys are in the union K'
|
||||||
Omit__LeanDocument_this_.Exclude_keyofDocument._id-or-id-or-__v_-or-%24isSingleNested_:
|
Omit__LeanDocument_this_.Exclude_keyofDocument._id-or-id-or-__v_-or-%24isSingleNested_:
|
||||||
@@ -488,6 +453,41 @@ components:
|
|||||||
example: /Public/somefolder/some.file
|
example: /Public/somefolder/some.file
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
|
LoginPayload:
|
||||||
|
properties:
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
description: 'Username for user'
|
||||||
|
example: secretuser
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
description: 'Password for user'
|
||||||
|
example: secretpassword
|
||||||
|
required:
|
||||||
|
- username
|
||||||
|
- password
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
AuthorizeResponse:
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
description: 'Authorization code'
|
||||||
|
example: someRandomCryptoString
|
||||||
|
required:
|
||||||
|
- code
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
AuthorizePayload:
|
||||||
|
properties:
|
||||||
|
clientId:
|
||||||
|
type: string
|
||||||
|
description: 'Client ID'
|
||||||
|
example: clientID1
|
||||||
|
required:
|
||||||
|
- clientId
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
bearerAuth:
|
bearerAuth:
|
||||||
type: http
|
type: http
|
||||||
@@ -558,86 +558,6 @@ paths:
|
|||||||
-
|
-
|
||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
parameters: []
|
parameters: []
|
||||||
/:
|
|
||||||
get:
|
|
||||||
operationId: Home
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Ok
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
summary: 'Render index.html'
|
|
||||||
tags:
|
|
||||||
- Web
|
|
||||||
security: []
|
|
||||||
parameters: []
|
|
||||||
/SASLogon/login:
|
|
||||||
post:
|
|
||||||
operationId: Login
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Ok
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
properties:
|
|
||||||
user: {properties: {displayName: {type: string}, username: {type: string}, id: {type: number, format: double}}, required: [displayName, username, id], type: object}
|
|
||||||
loggedIn: {type: boolean}
|
|
||||||
required:
|
|
||||||
- user
|
|
||||||
- loggedIn
|
|
||||||
type: object
|
|
||||||
summary: 'Accept a valid username/password'
|
|
||||||
tags:
|
|
||||||
- Web
|
|
||||||
security: []
|
|
||||||
parameters: []
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/LoginPayload'
|
|
||||||
/SASLogon/authorize:
|
|
||||||
post:
|
|
||||||
operationId: Authorize
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Ok
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/AuthorizeResponse'
|
|
||||||
examples:
|
|
||||||
'Example 1':
|
|
||||||
value: {code: someRandomCryptoString}
|
|
||||||
summary: 'Accept a valid username/password, plus a CLIENT_ID, and return an AUTH_CODE'
|
|
||||||
tags:
|
|
||||||
- Web
|
|
||||||
security: []
|
|
||||||
parameters: []
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '#/components/schemas/AuthorizePayload'
|
|
||||||
/SASLogon/logout:
|
|
||||||
get:
|
|
||||||
operationId: Logout
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Ok
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema: {}
|
|
||||||
summary: 'Destroy the session stored in cookies'
|
|
||||||
tags:
|
|
||||||
- Web
|
|
||||||
security: []
|
|
||||||
parameters: []
|
|
||||||
/SASjsApi/client:
|
/SASjsApi/client:
|
||||||
post:
|
post:
|
||||||
operationId: CreateClient
|
operationId: CreateClient
|
||||||
@@ -1504,6 +1424,86 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/ExecuteReturnJsonPayload'
|
$ref: '#/components/schemas/ExecuteReturnJsonPayload'
|
||||||
|
/:
|
||||||
|
get:
|
||||||
|
operationId: Home
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
summary: 'Render index.html'
|
||||||
|
tags:
|
||||||
|
- Web
|
||||||
|
security: []
|
||||||
|
parameters: []
|
||||||
|
/SASLogon/login:
|
||||||
|
post:
|
||||||
|
operationId: Login
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
properties:
|
||||||
|
user: {properties: {displayName: {type: string}, username: {type: string}, id: {type: number, format: double}}, required: [displayName, username, id], type: object}
|
||||||
|
loggedIn: {type: boolean}
|
||||||
|
required:
|
||||||
|
- user
|
||||||
|
- loggedIn
|
||||||
|
type: object
|
||||||
|
summary: 'Accept a valid username/password'
|
||||||
|
tags:
|
||||||
|
- Web
|
||||||
|
security: []
|
||||||
|
parameters: []
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/LoginPayload'
|
||||||
|
/SASLogon/authorize:
|
||||||
|
post:
|
||||||
|
operationId: Authorize
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/AuthorizeResponse'
|
||||||
|
examples:
|
||||||
|
'Example 1':
|
||||||
|
value: {code: someRandomCryptoString}
|
||||||
|
summary: 'Accept a valid username/password, plus a CLIENT_ID, and return an AUTH_CODE'
|
||||||
|
tags:
|
||||||
|
- Web
|
||||||
|
security: []
|
||||||
|
parameters: []
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/AuthorizePayload'
|
||||||
|
/SASLogon/logout:
|
||||||
|
get:
|
||||||
|
operationId: Logout
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: {}
|
||||||
|
summary: 'Destroy the session stored in cookies'
|
||||||
|
tags:
|
||||||
|
- Web
|
||||||
|
security: []
|
||||||
|
parameters: []
|
||||||
servers:
|
servers:
|
||||||
-
|
-
|
||||||
url: /
|
url: /
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import express, { ErrorRequestHandler } from 'express'
|
import express, { ErrorRequestHandler } from 'express'
|
||||||
|
import mongoose from 'mongoose'
|
||||||
import csrf from 'csurf'
|
import csrf from 'csurf'
|
||||||
import session from 'express-session'
|
import session from 'express-session'
|
||||||
import MongoStore from 'connect-mongo'
|
import MongoStore from 'connect-mongo'
|
||||||
@@ -97,45 +98,44 @@ if (CORS === CorsType.ENABLED) {
|
|||||||
app.use(cors({ credentials: true, origin: whiteList }))
|
app.use(cors({ credentials: true, origin: whiteList }))
|
||||||
}
|
}
|
||||||
|
|
||||||
/***********************************
|
export default setProcessVariables().then(async () => {
|
||||||
* DB Connection & *
|
/***********************************
|
||||||
* Express Sessions *
|
* DB Connection & *
|
||||||
* With Mongo Store *
|
* Express Sessions *
|
||||||
***********************************/
|
* With Mongo Store *
|
||||||
if (MODE === ModeType.Server) {
|
***********************************/
|
||||||
let store: MongoStore | undefined
|
if (MODE === ModeType.Server) {
|
||||||
|
let store: MongoStore | undefined
|
||||||
|
|
||||||
// NOTE: when exporting app.js as agent for supertest
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
// we should exclude connecting to the real database
|
store = MongoStore.create({
|
||||||
if (process.env.NODE_ENV !== 'test') {
|
client: mongoose.connection!.getClient() as any,
|
||||||
const clientPromise = connectDB().then((conn) => conn!.getClient() as any)
|
collectionName: 'sessions'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
store = MongoStore.create({ clientPromise, collectionName: 'sessions' })
|
app.use(
|
||||||
|
session({
|
||||||
|
secret: process.secrets.SESSION_SECRET,
|
||||||
|
saveUninitialized: false, // don't create session until something stored
|
||||||
|
resave: false, //don't save session if unmodified
|
||||||
|
store,
|
||||||
|
cookie: cookieOptions
|
||||||
|
})
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(
|
app.use(express.json({ limit: '100mb' }))
|
||||||
session({
|
app.use(express.static(path.join(__dirname, '../public')))
|
||||||
secret: process.env.SESSION_SECRET as string,
|
|
||||||
saveUninitialized: false, // don't create session until something stored
|
|
||||||
resave: false, //don't save session if unmodified
|
|
||||||
store,
|
|
||||||
cookie: cookieOptions
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use(express.json({ limit: '100mb' }))
|
const onError: ErrorRequestHandler = (err, req, res, next) => {
|
||||||
app.use(express.static(path.join(__dirname, '../public')))
|
if (err.code === 'EBADCSRFTOKEN')
|
||||||
|
return res.status(400).send('Invalid CSRF token!')
|
||||||
|
|
||||||
const onError: ErrorRequestHandler = (err, req, res, next) => {
|
console.error(err.stack)
|
||||||
if (err.code === 'EBADCSRFTOKEN')
|
res.status(500).send('Something broke!')
|
||||||
return res.status(400).send('Invalid CSRF token!')
|
}
|
||||||
|
|
||||||
console.error(err.stack)
|
|
||||||
res.status(500).send('Something broke!')
|
|
||||||
}
|
|
||||||
|
|
||||||
export default setProcessVariables().then(async () => {
|
|
||||||
await setupFolders()
|
await setupFolders()
|
||||||
await copySASjsCore()
|
await copySASjsCore()
|
||||||
|
|
||||||
|
|||||||
@@ -129,8 +129,8 @@ const verifyAuthCode = async (
|
|||||||
clientId: string,
|
clientId: string,
|
||||||
code: string
|
code: string
|
||||||
): Promise<InfoJWT | undefined> => {
|
): Promise<InfoJWT | undefined> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve) => {
|
||||||
jwt.verify(code, process.env.AUTH_CODE_SECRET as string, (err, data) => {
|
jwt.verify(code, process.secrets.AUTH_CODE_SECRET, (err, data) => {
|
||||||
if (err) return resolve(undefined)
|
if (err) return resolve(undefined)
|
||||||
|
|
||||||
const clientInfo: InfoJWT = {
|
const clientInfo: InfoJWT = {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export interface GroupResponse {
|
|||||||
description: string
|
description: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GroupDetailsResponse {
|
export interface GroupDetailsResponse {
|
||||||
groupId: number
|
groupId: number
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
@@ -249,9 +249,10 @@ const updateUsersListInGroup = async (
|
|||||||
message: 'User not found.'
|
message: 'User not found.'
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedGroup = (action === 'addUser'
|
const updatedGroup =
|
||||||
? await group.addUser(user._id)
|
action === 'addUser'
|
||||||
: await group.removeUser(user._id)) as unknown as GroupDetailsResponse
|
? await group.addUser(user)
|
||||||
|
: await group.removeUser(user)
|
||||||
|
|
||||||
if (!updatedGroup)
|
if (!updatedGroup)
|
||||||
throw {
|
throw {
|
||||||
@@ -260,9 +261,6 @@ const updateUsersListInGroup = async (
|
|||||||
message: 'Unable to update group.'
|
message: 'Unable to update group.'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action === 'addUser') user.addGroup(group._id)
|
|
||||||
else user.removeGroup(group._id)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
groupId: updatedGroup.groupId,
|
groupId: updatedGroup.groupId,
|
||||||
name: updatedGroup.name,
|
name: updatedGroup.name,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export const authenticateAccessToken: RequestHandler = async (
|
|||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
next,
|
next,
|
||||||
process.env.ACCESS_TOKEN_SECRET as string,
|
process.secrets.ACCESS_TOKEN_SECRET,
|
||||||
'accessToken'
|
'accessToken'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -45,7 +45,7 @@ export const authenticateRefreshToken: RequestHandler = (req, res, next) => {
|
|||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
next,
|
next,
|
||||||
process.env.REFRESH_TOKEN_SECRET as string,
|
process.secrets.REFRESH_TOKEN_SECRET,
|
||||||
'refreshToken'
|
'refreshToken'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
45
api/src/model/Configuration.ts
Normal file
45
api/src/model/Configuration.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import mongoose, { Schema } from 'mongoose'
|
||||||
|
|
||||||
|
export interface ConfigurationType {
|
||||||
|
/**
|
||||||
|
* SecretOrPrivateKey to sign Access Token
|
||||||
|
* @example "someRandomCryptoString"
|
||||||
|
*/
|
||||||
|
ACCESS_TOKEN_SECRET: string
|
||||||
|
/**
|
||||||
|
* SecretOrPrivateKey to sign Refresh Token
|
||||||
|
* @example "someRandomCryptoString"
|
||||||
|
*/
|
||||||
|
REFRESH_TOKEN_SECRET: string
|
||||||
|
/**
|
||||||
|
* SecretOrPrivateKey to sign Auth Code
|
||||||
|
* @example "someRandomCryptoString"
|
||||||
|
*/
|
||||||
|
AUTH_CODE_SECRET: string
|
||||||
|
/**
|
||||||
|
* Secret used to sign the session cookie
|
||||||
|
* @example "someRandomCryptoString"
|
||||||
|
*/
|
||||||
|
SESSION_SECRET: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfigurationSchema = new Schema<ConfigurationType>({
|
||||||
|
ACCESS_TOKEN_SECRET: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
REFRESH_TOKEN_SECRET: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
AUTH_CODE_SECRET: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
SESSION_SECRET: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default mongoose.model('Configuration', ConfigurationSchema)
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import mongoose, { Schema, model, Document, Model } from 'mongoose'
|
import mongoose, { Schema, model, Document, Model } from 'mongoose'
|
||||||
import User from './User'
|
import { GroupDetailsResponse } from '../controllers'
|
||||||
|
import User, { IUser } from './User'
|
||||||
const AutoIncrement = require('mongoose-sequence')(mongoose)
|
const AutoIncrement = require('mongoose-sequence')(mongoose)
|
||||||
|
|
||||||
export interface GroupPayload {
|
export interface GroupPayload {
|
||||||
@@ -27,8 +28,9 @@ interface IGroupDocument extends GroupPayload, Document {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface IGroup extends IGroupDocument {
|
interface IGroup extends IGroupDocument {
|
||||||
addUser(userObjectId: Schema.Types.ObjectId): Promise<IGroup>
|
addUser(user: IUser): Promise<GroupDetailsResponse>
|
||||||
removeUser(userObjectId: Schema.Types.ObjectId): Promise<IGroup>
|
removeUser(user: IUser): Promise<GroupDetailsResponse>
|
||||||
|
hasUser(user: IUser): boolean
|
||||||
}
|
}
|
||||||
interface IGroupModel extends Model<IGroup> {}
|
interface IGroupModel extends Model<IGroup> {}
|
||||||
|
|
||||||
@@ -70,28 +72,31 @@ groupSchema.pre('remove', async function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Instance Methods
|
// Instance Methods
|
||||||
groupSchema.method(
|
groupSchema.method('addUser', async function (user: IUser) {
|
||||||
'addUser',
|
const userObjectId = user._id
|
||||||
async function (userObjectId: Schema.Types.ObjectId) {
|
const userIdIndex = this.users.indexOf(userObjectId)
|
||||||
const userIdIndex = this.users.indexOf(userObjectId)
|
if (userIdIndex === -1) {
|
||||||
if (userIdIndex === -1) {
|
this.users.push(userObjectId)
|
||||||
this.users.push(userObjectId)
|
user.addGroup(this._id)
|
||||||
}
|
|
||||||
this.markModified('users')
|
|
||||||
return this.save()
|
|
||||||
}
|
}
|
||||||
)
|
this.markModified('users')
|
||||||
groupSchema.method(
|
return this.save()
|
||||||
'removeUser',
|
})
|
||||||
async function (userObjectId: Schema.Types.ObjectId) {
|
groupSchema.method('removeUser', async function (user: IUser) {
|
||||||
const userIdIndex = this.users.indexOf(userObjectId)
|
const userObjectId = user._id
|
||||||
if (userIdIndex > -1) {
|
const userIdIndex = this.users.indexOf(userObjectId)
|
||||||
this.users.splice(userIdIndex, 1)
|
if (userIdIndex > -1) {
|
||||||
}
|
this.users.splice(userIdIndex, 1)
|
||||||
this.markModified('users')
|
user.removeGroup(this._id)
|
||||||
return this.save()
|
|
||||||
}
|
}
|
||||||
)
|
this.markModified('users')
|
||||||
|
return this.save()
|
||||||
|
})
|
||||||
|
groupSchema.method('hasUser', function (user: IUser) {
|
||||||
|
const userObjectId = user._id
|
||||||
|
const userIdIndex = this.users.indexOf(userObjectId)
|
||||||
|
return userIdIndex > -1
|
||||||
|
})
|
||||||
|
|
||||||
export const Group: IGroupModel = model<IGroup, IGroupModel>(
|
export const Group: IGroupModel = model<IGroup, IGroupModel>(
|
||||||
'Group',
|
'Group',
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export interface UserPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface IUserDocument extends UserPayload, Document {
|
interface IUserDocument extends UserPayload, Document {
|
||||||
|
_id: Schema.Types.ObjectId
|
||||||
id: number
|
id: number
|
||||||
isAdmin: boolean
|
isAdmin: boolean
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
@@ -43,7 +44,7 @@ interface IUserDocument extends UserPayload, Document {
|
|||||||
tokens: [{ [key: string]: string }]
|
tokens: [{ [key: string]: string }]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IUser extends IUserDocument {
|
export interface IUser extends IUserDocument {
|
||||||
comparePassword(password: string): boolean
|
comparePassword(password: string): boolean
|
||||||
addGroup(groupObjectId: Schema.Types.ObjectId): Promise<IUser>
|
addGroup(groupObjectId: Schema.Types.ObjectId): Promise<IUser>
|
||||||
removeGroup(groupObjectId: Schema.Types.ObjectId): Promise<IUser>
|
removeGroup(groupObjectId: Schema.Types.ObjectId): Promise<IUser>
|
||||||
|
|||||||
1
api/src/types/system/process.d.ts
vendored
1
api/src/types/system/process.d.ts
vendored
@@ -8,5 +8,6 @@ declare namespace NodeJS {
|
|||||||
appStreamConfig: import('../').AppStreamConfig
|
appStreamConfig: import('../').AppStreamConfig
|
||||||
logger: import('@sasjs/utils/logger').Logger
|
logger: import('@sasjs/utils/logger').Logger
|
||||||
runTimes: import('../../utils').RunTimeType[]
|
runTimes: import('../../utils').RunTimeType[]
|
||||||
|
secrets: import('../../model/Configuration').ConfigurationType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,5 @@ export const connectDB = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('Connected to DB!')
|
console.log('Connected to DB!')
|
||||||
await seedDB()
|
return seedDB()
|
||||||
|
|
||||||
return mongoose.connection
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ import jwt from 'jsonwebtoken'
|
|||||||
import { InfoJWT } from '../types'
|
import { InfoJWT } from '../types'
|
||||||
|
|
||||||
export const generateAccessToken = (data: InfoJWT) =>
|
export const generateAccessToken = (data: InfoJWT) =>
|
||||||
jwt.sign(data, process.env.ACCESS_TOKEN_SECRET as string, {
|
jwt.sign(data, process.secrets.ACCESS_TOKEN_SECRET, {
|
||||||
expiresIn: '1day'
|
expiresIn: '1day'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ import jwt from 'jsonwebtoken'
|
|||||||
import { InfoJWT } from '../types'
|
import { InfoJWT } from '../types'
|
||||||
|
|
||||||
export const generateAuthCode = (data: InfoJWT) =>
|
export const generateAuthCode = (data: InfoJWT) =>
|
||||||
jwt.sign(data, process.env.AUTH_CODE_SECRET as string, {
|
jwt.sign(data, process.secrets.AUTH_CODE_SECRET, {
|
||||||
expiresIn: '30s'
|
expiresIn: '30s'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ import jwt from 'jsonwebtoken'
|
|||||||
import { InfoJWT } from '../types'
|
import { InfoJWT } from '../types'
|
||||||
|
|
||||||
export const generateRefreshToken = (data: InfoJWT) =>
|
export const generateRefreshToken = (data: InfoJWT) =>
|
||||||
jwt.sign(data, process.env.REFRESH_TOKEN_SECRET as string, {
|
jwt.sign(data, process.secrets.REFRESH_TOKEN_SECRET, {
|
||||||
expiresIn: '30 days'
|
expiresIn: '30 days'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { RunTimeType } from '.'
|
|||||||
|
|
||||||
export const getRunTimeAndFilePath = async (programPath: string) => {
|
export const getRunTimeAndFilePath = async (programPath: string) => {
|
||||||
const ext = path.extname(programPath)
|
const ext = path.extname(programPath)
|
||||||
// If programPath (_program) is provided with a ".sas" or ".js" extension
|
// If programPath (_program) is provided with a ".sas" or ".js" extension
|
||||||
// we should use that extension to determine the appropriate runTime
|
// we should use that extension to determine the appropriate runTime
|
||||||
if (ext && Object.values(RunTimeType).includes(ext.slice(1) as RunTimeType)) {
|
if (ext && Object.values(RunTimeType).includes(ext.slice(1) as RunTimeType)) {
|
||||||
const runTime = ext.slice(1)
|
const runTime = ext.slice(1)
|
||||||
|
|||||||
@@ -1,6 +1,73 @@
|
|||||||
import Client from '../model/Client'
|
import Client from '../model/Client'
|
||||||
|
import Group from '../model/Group'
|
||||||
import User from '../model/User'
|
import User from '../model/User'
|
||||||
|
import Configuration, { ConfigurationType } from '../model/Configuration'
|
||||||
|
|
||||||
|
import { randomBytes } from 'crypto'
|
||||||
|
|
||||||
|
export const SECRETS: ConfigurationType = {
|
||||||
|
ACCESS_TOKEN_SECRET: randomBytes(64).toString('hex'),
|
||||||
|
REFRESH_TOKEN_SECRET: randomBytes(64).toString('hex'),
|
||||||
|
AUTH_CODE_SECRET: randomBytes(64).toString('hex'),
|
||||||
|
SESSION_SECRET: randomBytes(64).toString('hex')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const seedDB = async (): Promise<ConfigurationType> => {
|
||||||
|
// Checking if client is already in the database
|
||||||
|
const clientExist = await Client.findOne({ clientId: CLIENT.clientId })
|
||||||
|
if (!clientExist) {
|
||||||
|
const client = new Client(CLIENT)
|
||||||
|
await client.save()
|
||||||
|
|
||||||
|
console.log(`DB Seed - client created: ${CLIENT.clientId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checking if 'AllUsers' Group is already in the database
|
||||||
|
let groupExist = await Group.findOne({ name: GROUP.name })
|
||||||
|
if (!groupExist) {
|
||||||
|
const group = new Group(GROUP)
|
||||||
|
groupExist = await group.save()
|
||||||
|
|
||||||
|
console.log(`DB Seed - Group created: ${GROUP.name}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checking if user is already in the database
|
||||||
|
let usernameExist = await User.findOne({ username: ADMIN_USER.username })
|
||||||
|
if (!usernameExist) {
|
||||||
|
const user = new User(ADMIN_USER)
|
||||||
|
usernameExist = await user.save()
|
||||||
|
|
||||||
|
console.log(`DB Seed - admin account created: ${ADMIN_USER.username}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!groupExist.hasUser(usernameExist)) {
|
||||||
|
groupExist.addUser(usernameExist)
|
||||||
|
console.log(
|
||||||
|
`DB Seed - admin account '${ADMIN_USER.username}' added to Group '${GROUP.name}'`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checking if configuration is present in the database
|
||||||
|
let configExist = await Configuration.findOne()
|
||||||
|
if (!configExist) {
|
||||||
|
const configuration = new Configuration(SECRETS)
|
||||||
|
configExist = await configuration.save()
|
||||||
|
|
||||||
|
console.log('DB Seed - configuration added')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ACCESS_TOKEN_SECRET: configExist.ACCESS_TOKEN_SECRET,
|
||||||
|
REFRESH_TOKEN_SECRET: configExist.REFRESH_TOKEN_SECRET,
|
||||||
|
AUTH_CODE_SECRET: configExist.AUTH_CODE_SECRET,
|
||||||
|
SESSION_SECRET: configExist.SESSION_SECRET
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const GROUP = {
|
||||||
|
name: 'AllUsers',
|
||||||
|
description: 'Group contains all users'
|
||||||
|
}
|
||||||
const CLIENT = {
|
const CLIENT = {
|
||||||
clientId: 'clientID1',
|
clientId: 'clientID1',
|
||||||
clientSecret: 'clientSecret'
|
clientSecret: 'clientSecret'
|
||||||
@@ -13,23 +80,3 @@ const ADMIN_USER = {
|
|||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
isActive: true
|
isActive: true
|
||||||
}
|
}
|
||||||
|
|
||||||
export const seedDB = async () => {
|
|
||||||
// Checking if client is already in the database
|
|
||||||
const clientExist = await Client.findOne({ clientId: CLIENT.clientId })
|
|
||||||
if (!clientExist) {
|
|
||||||
const client = new Client(CLIENT)
|
|
||||||
await client.save()
|
|
||||||
|
|
||||||
console.log(`DB Seed - client created: ${CLIENT.clientId}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checking if user is already in the database
|
|
||||||
const usernameExist = await User.findOne({ username: ADMIN_USER.username })
|
|
||||||
if (!usernameExist) {
|
|
||||||
const user = new User(ADMIN_USER)
|
|
||||||
await user.save()
|
|
||||||
|
|
||||||
console.log(`DB Seed - admin account created: ${ADMIN_USER.username}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,16 +1,28 @@
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { createFolder, getAbsolutePath, getRealPath } from '@sasjs/utils'
|
import { createFolder, getAbsolutePath, getRealPath } from '@sasjs/utils'
|
||||||
|
|
||||||
import { getDesktopFields, ModeType, RunTimeType } from '.'
|
import { connectDB, getDesktopFields, ModeType, RunTimeType, SECRETS } from '.'
|
||||||
|
|
||||||
export const setProcessVariables = async () => {
|
export const setProcessVariables = async () => {
|
||||||
|
const { MODE, RUN_TIMES } = process.env
|
||||||
|
|
||||||
|
if (MODE === ModeType.Server) {
|
||||||
|
// NOTE: when exporting app.js as agent for supertest
|
||||||
|
// it should prevent connecting to the real database
|
||||||
|
if (process.env.NODE_ENV !== 'test') {
|
||||||
|
const secrets = await connectDB()
|
||||||
|
|
||||||
|
process.secrets = secrets
|
||||||
|
} else {
|
||||||
|
process.secrets = SECRETS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV === 'test') {
|
if (process.env.NODE_ENV === 'test') {
|
||||||
process.driveLoc = path.join(process.cwd(), 'sasjs_root')
|
process.driveLoc = path.join(process.cwd(), 'sasjs_root')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { MODE, RUN_TIMES } = process.env
|
|
||||||
|
|
||||||
process.runTimes = (RUN_TIMES?.split(',') as RunTimeType[]) ?? []
|
process.runTimes = (RUN_TIMES?.split(',') as RunTimeType[]) ?? []
|
||||||
|
|
||||||
if (MODE === ModeType.Server) {
|
if (MODE === ModeType.Server) {
|
||||||
|
|||||||
@@ -78,33 +78,7 @@ const verifyMODE = (): string[] => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.MODE === ModeType.Server) {
|
if (process.env.MODE === ModeType.Server) {
|
||||||
const {
|
const { DB_CONNECT } = process.env
|
||||||
ACCESS_TOKEN_SECRET,
|
|
||||||
REFRESH_TOKEN_SECRET,
|
|
||||||
AUTH_CODE_SECRET,
|
|
||||||
SESSION_SECRET,
|
|
||||||
DB_CONNECT
|
|
||||||
} = process.env
|
|
||||||
|
|
||||||
if (!ACCESS_TOKEN_SECRET)
|
|
||||||
errors.push(
|
|
||||||
`- ACCESS_TOKEN_SECRET is required for PROTOCOL '${ModeType.Server}'`
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!REFRESH_TOKEN_SECRET)
|
|
||||||
errors.push(
|
|
||||||
`- REFRESH_TOKEN_SECRET is required for PROTOCOL '${ModeType.Server}'`
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!AUTH_CODE_SECRET)
|
|
||||||
errors.push(
|
|
||||||
`- AUTH_CODE_SECRET is required for PROTOCOL '${ModeType.Server}'`
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!SESSION_SECRET)
|
|
||||||
errors.push(
|
|
||||||
`- SESSION_SECRET is required for PROTOCOL '${ModeType.Server}'`
|
|
||||||
)
|
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== 'test')
|
if (process.env.NODE_ENV !== 'test')
|
||||||
if (!DB_CONNECT)
|
if (!DB_CONNECT)
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ export const extractJSONFromZip = async (zipFile: Express.Multer.File) => {
|
|||||||
|
|
||||||
for await (const entry of zip) {
|
for await (const entry of zip) {
|
||||||
const fileName = entry.path as string
|
const fileName = entry.path as string
|
||||||
if (fileName.toUpperCase().endsWith('.JSON') && fileName === fileInZip) {
|
// grab the first json found in .zip
|
||||||
|
if (fileName.toUpperCase().endsWith('.JSON')) {
|
||||||
fileContent = await entry.buffer()
|
fileContent = await entry.buffer()
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user