1
0
mirror of https://github.com/sasjs/server.git synced 2025-12-10 19:34:34 +00:00

Compare commits

..

13 Commits

Author SHA1 Message Date
semantic-release-bot
737b34567e chore(release): 0.30.0 [skip ci]
# [0.30.0](https://github.com/sasjs/server/compare/v0.29.0...v0.30.0) (2023-02-28)

### Bug Fixes

* lint + remove default settings ([3de59ac](3de59ac4f8))

### Features

* add new env config DB_TYPE ([158f044](158f044363))
2023-02-28 21:08:30 +00:00
Allan Bowe
6373442f83 Merge pull request #340 from sasjs/issue-339
feat: add new env config DB_TYPE
2023-02-28 21:04:25 +00:00
munja
3de59ac4f8 fix: lint + remove default settings 2023-02-28 21:01:39 +00:00
Allan Bowe
941988cd7c chore(docs): linking to official docs 2023-02-28 20:55:32 +00:00
158f044363 feat: add new env config DB_TYPE 2023-03-01 01:41:08 +05:00
semantic-release-bot
02ae041a81 chore(release): 0.29.0 [skip ci]
# [0.29.0](https://github.com/sasjs/server/compare/v0.28.7...v0.29.0) (2023-02-06)

### Features

* Add /SASjsApi endpoint in permissions ([b3402ea](b3402ea80a))
2023-02-06 13:07:06 +00:00
Allan Bowe
c4c84b1537 Merge pull request #338 from sasjs/issue-224
feat: Add /SASjsApi endpoint in permissions
2023-02-06 13:02:49 +00:00
b3402ea80a feat: Add /SASjsApi endpoint in permissions 2023-02-06 15:29:24 +05:00
semantic-release-bot
abe942e697 chore(release): 0.28.7 [skip ci]
## [0.28.7](https://github.com/sasjs/server/compare/v0.28.6...v0.28.7) (2023-02-03)

### Bug Fixes

* add user to all users group on user creation ([2bae52e](2bae52e307))
2023-02-03 13:48:40 +00:00
Allan Bowe
faf2edb111 Merge pull request #337 from sasjs/issue-336
fix: add user to all users group on user creation
2023-02-03 13:44:46 +00:00
5bec453e89 chore: quick fix 2023-02-03 18:39:35 +05:00
7f2174dd2c chore: quick fix 2023-02-03 16:48:18 +05:00
2bae52e307 fix: add user to all users group on user creation 2023-02-03 16:47:18 +05:00
10 changed files with 132 additions and 20 deletions

View File

@@ -1,3 +1,29 @@
# [0.30.0](https://github.com/sasjs/server/compare/v0.29.0...v0.30.0) (2023-02-28)
### Bug Fixes
* lint + remove default settings ([3de59ac](https://github.com/sasjs/server/commit/3de59ac4f8e3d95cad31f09e6963bd04c4811f26))
### Features
* add new env config DB_TYPE ([158f044](https://github.com/sasjs/server/commit/158f044363abf2576c8248f0ca9da4bc9cb7e9d8))
# [0.29.0](https://github.com/sasjs/server/compare/v0.28.7...v0.29.0) (2023-02-06)
### Features
* Add /SASjsApi endpoint in permissions ([b3402ea](https://github.com/sasjs/server/commit/b3402ea80afb8802eee8b8b6cbbbcc29903424bc))
## [0.28.7](https://github.com/sasjs/server/compare/v0.28.6...v0.28.7) (2023-02-03)
### Bug Fixes
* add user to all users group on user creation ([2bae52e](https://github.com/sasjs/server/commit/2bae52e307327d7ee4a94b19d843abdc0ccec9d1))
## [0.28.6](https://github.com/sasjs/server/compare/v0.28.5...v0.28.6) (2023-01-26) ## [0.28.6](https://github.com/sasjs/server/compare/v0.28.5...v0.28.6) (2023-01-26)

View File

@@ -137,6 +137,9 @@ CA_ROOT=fullchain.pem (optional)
## ENV variables required for MODE: `server` ## ENV variables required for MODE: `server`
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: [mongodb|cosmos_mongodb] default: mongodb
DB_TYPE=
# AUTH_PROVIDERS options: [ldap] default: `` # AUTH_PROVIDERS options: [ldap] default: ``
AUTH_PROVIDERS= AUTH_PROVIDERS=

View File

@@ -14,6 +14,7 @@ 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
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
DB_TYPE=[mongodb|cosmos_mongodb] default considered as mongodb
AUTH_PROVIDERS=[ldap] AUTH_PROVIDERS=[ldap]

View File

@@ -3,19 +3,27 @@ import mongoose from 'mongoose'
import session from 'express-session' import session from 'express-session'
import MongoStore from 'connect-mongo' import MongoStore from 'connect-mongo'
import { ModeType, ProtocolType } from '../utils' import { DatabaseType, ModeType, ProtocolType } from '../utils'
export const configureExpressSession = (app: Express) => { export const configureExpressSession = (app: Express) => {
const { MODE } = process.env const { MODE, DB_TYPE } = process.env
if (MODE === ModeType.Server) { if (MODE === ModeType.Server) {
let store: MongoStore | undefined let store: MongoStore | undefined
if (process.env.NODE_ENV !== 'test') { if (process.env.NODE_ENV !== 'test') {
store = MongoStore.create({ if (DB_TYPE === DatabaseType.COSMOS_MONGODB) {
client: mongoose.connection!.getClient() as any, // COSMOS DB requires specific connection options (compatibility mode)
collectionName: 'sessions' // See: https://www.npmjs.com/package/connect-mongo#set-the-compatibility-mode
}) store = MongoStore.create({
client: mongoose.connection!.getClient() as any,
autoRemove: 'interval'
})
} else {
store = MongoStore.create({
client: mongoose.connection!.getClient() as any
})
}
} }
const { PROTOCOL, ALLOWED_DOMAIN } = process.env const { PROTOCOL, ALLOWED_DOMAIN } = process.env

View File

@@ -159,7 +159,7 @@ const updatePassword = async (
) => { ) => {
const { currentPassword, newPassword } = data const { currentPassword, newPassword } = data
const userId = req.user?.userId const userId = req.user?.userId
const dbUser = await User.findOne({ userId }) const dbUser = await User.findOne({ id: userId })
if (!dbUser) if (!dbUser)
throw { throw {

View File

@@ -21,9 +21,9 @@ import {
getUserAutoExec, getUserAutoExec,
updateUserAutoExec, updateUserAutoExec,
ModeType, ModeType,
AuthProviderType ALL_USERS_GROUP
} from '../utils' } from '../utils'
import { GroupResponse } from './group' import { GroupController, GroupResponse } from './group'
export interface UserResponse { export interface UserResponse {
id: number id: number
@@ -237,6 +237,15 @@ const createUser = async (data: UserPayload): Promise<UserDetailsResponse> => {
const savedUser = await user.save() const savedUser = await user.save()
const groupController = new GroupController()
const allUsersGroup = await groupController
.getGroupByGroupName(ALL_USERS_GROUP.name)
.catch(() => {})
if (allUsersGroup) {
await groupController.addUserToGroup(allUsersGroup.groupId, savedUser.id)
}
return { return {
id: savedUser.id, id: savedUser.id,
displayName: savedUser.displayName, displayName: savedUser.displayName,

View File

@@ -5,7 +5,7 @@ import {
PermissionSettingForRoute, PermissionSettingForRoute,
PermissionType PermissionType
} from '../controllers/permission' } from '../controllers/permission'
import { getPath, isPublicRoute } from '../utils' import { getPath, isPublicRoute, TopLevelRoutes } from '../utils'
export const authorize: RequestHandler = async (req, res, next) => { export const authorize: RequestHandler = async (req, res, next) => {
const { user } = req const { user } = req
@@ -22,6 +22,9 @@ export const authorize: RequestHandler = async (req, res, next) => {
if (!dbUser) return res.sendStatus(401) if (!dbUser) return res.sendStatus(401)
const path = getPath(req) const path = getPath(req)
const { baseUrl } = req
const topLevelRoute =
TopLevelRoutes.find((route) => baseUrl.startsWith(route)) || baseUrl
// find permission w.r.t user // find permission w.r.t user
const permission = await Permission.findOne({ const permission = await Permission.findOne({
@@ -35,6 +38,21 @@ export const authorize: RequestHandler = async (req, res, next) => {
else return res.sendStatus(401) else return res.sendStatus(401)
} }
// find permission w.r.t user on top level
const topLevelPermission = await Permission.findOne({
path: topLevelRoute,
type: PermissionType.route,
user: dbUser._id
})
if (topLevelPermission) {
if (topLevelPermission.setting === PermissionSettingForRoute.grant)
return next()
else return res.sendStatus(401)
}
let isPermissionDenied = false
// find permission w.r.t user's groups // find permission w.r.t user's groups
for (const group of dbUser.groups) { for (const group of dbUser.groups) {
const groupPermission = await Permission.findOne({ const groupPermission = await Permission.findOne({
@@ -42,8 +60,28 @@ export const authorize: RequestHandler = async (req, res, next) => {
type: PermissionType.route, type: PermissionType.route,
group group
}) })
if (groupPermission?.setting === PermissionSettingForRoute.grant)
return next() if (groupPermission) {
if (groupPermission.setting === PermissionSettingForRoute.grant) {
return next()
} else {
isPermissionDenied = true
}
}
} }
if (!isPermissionDenied) {
// find permission w.r.t user's groups on top level
for (const group of dbUser.groups) {
const groupPermission = await Permission.findOne({
path: topLevelRoute,
type: PermissionType.route,
group
})
if (groupPermission?.setting === PermissionSettingForRoute.grant)
return next()
}
}
return res.sendStatus(401) return res.sendStatus(401)
} }

View File

@@ -1,7 +1,8 @@
import { Request } from 'express' import { Request } from 'express'
export const TopLevelRoutes = ['/AppStream', '/SASjsApi']
const StaticAuthorizedRoutes = [ const StaticAuthorizedRoutes = [
'/AppStream',
'/SASjsApi/code/execute', '/SASjsApi/code/execute',
'/SASjsApi/stp/execute', '/SASjsApi/stp/execute',
'/SASjsApi/drive/deploy', '/SASjsApi/drive/deploy',
@@ -15,7 +16,7 @@ const StaticAuthorizedRoutes = [
export const getAuthorizedRoutes = () => { export const getAuthorizedRoutes = () => {
const streamingApps = Object.keys(process.appStreamConfig) const streamingApps = Object.keys(process.appStreamConfig)
const streamingAppsRoutes = streamingApps.map((app) => `/AppStream/${app}`) const streamingAppsRoutes = streamingApps.map((app) => `/AppStream/${app}`)
return [...StaticAuthorizedRoutes, ...streamingAppsRoutes] return [...TopLevelRoutes, ...StaticAuthorizedRoutes, ...streamingAppsRoutes]
} }
export const getPath = (req: Request) => { export const getPath = (req: Request) => {

View File

@@ -23,12 +23,12 @@ export const seedDB = async (): Promise<ConfigurationType> => {
} }
// Checking if 'AllUsers' Group is already in the database // Checking if 'AllUsers' Group is already in the database
let groupExist = await Group.findOne({ name: GROUP.name }) let groupExist = await Group.findOne({ name: ALL_USERS_GROUP.name })
if (!groupExist) { if (!groupExist) {
const group = new Group(GROUP) const group = new Group(ALL_USERS_GROUP)
groupExist = await group.save() groupExist = await group.save()
process.logger.success(`DB Seed - Group created: ${GROUP.name}`) process.logger.success(`DB Seed - Group created: ${ALL_USERS_GROUP.name}`)
} }
// Checking if 'Public' Group is already in the database // Checking if 'Public' Group is already in the database
@@ -54,7 +54,7 @@ export const seedDB = async (): Promise<ConfigurationType> => {
if (!groupExist.hasUser(usernameExist)) { if (!groupExist.hasUser(usernameExist)) {
groupExist.addUser(usernameExist) groupExist.addUser(usernameExist)
process.logger.success( process.logger.success(
`DB Seed - admin account '${ADMIN_USER.username}' added to Group '${GROUP.name}'` `DB Seed - admin account '${ADMIN_USER.username}' added to Group '${ALL_USERS_GROUP.name}'`
) )
} }
@@ -75,7 +75,7 @@ export const seedDB = async (): Promise<ConfigurationType> => {
} }
} }
const GROUP = { export const ALL_USERS_GROUP = {
name: 'AllUsers', name: 'AllUsers',
description: 'Group contains all users' description: 'Group contains all users'
} }

View File

@@ -47,6 +47,11 @@ export enum ReturnCode {
InvalidEnv InvalidEnv
} }
export enum DatabaseType {
MONGO = 'mongodb',
COSMOS_MONGODB = 'cosmos_mongodb'
}
export const verifyEnvVariables = (): ReturnCode => { export const verifyEnvVariables = (): ReturnCode => {
const errors: string[] = [] const errors: string[] = []
@@ -70,6 +75,8 @@ export const verifyEnvVariables = (): ReturnCode => {
errors.push(...verifyLDAPVariables()) errors.push(...verifyLDAPVariables())
errors.push(...verifyDbType())
if (errors.length) { if (errors.length) {
process.logger?.error( process.logger?.error(
`Invalid environment variable(s) provided: \n${errors.join('\n')}` `Invalid environment variable(s) provided: \n${errors.join('\n')}`
@@ -342,11 +349,30 @@ const verifyLDAPVariables = () => {
return errors return errors
} }
const verifyDbType = () => {
const errors: string[] = []
const { MODE, DB_TYPE } = process.env
if (MODE === ModeType.Server) {
if (DB_TYPE) {
const dbTypes = Object.values(DatabaseType)
if (!dbTypes.includes(DB_TYPE as DatabaseType))
errors.push(`- DB_TYPE '${DB_TYPE}'\n - valid options ${dbTypes}`)
} else {
process.env.DB_TYPE = DEFAULTS.DB_TYPE
}
}
return errors
}
const DEFAULTS = { const DEFAULTS = {
MODE: ModeType.Desktop, MODE: ModeType.Desktop,
PROTOCOL: ProtocolType.HTTP, PROTOCOL: ProtocolType.HTTP,
PORT: '5000', PORT: '5000',
HELMET_COEP: HelmetCoepType.TRUE, HELMET_COEP: HelmetCoepType.TRUE,
LOG_FORMAT_MORGAN: LOG_FORMAT_MORGANType.Common, LOG_FORMAT_MORGAN: LOG_FORMAT_MORGANType.Common,
RUN_TIMES: RunTimeType.SAS RUN_TIMES: RunTimeType.SAS,
DB_TYPE: DatabaseType.MONGO
} }