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

Compare commits

...

19 Commits

Author SHA1 Message Date
semantic-release-bot
6b666d5554 chore(release): 0.27.0 [skip ci]
# [0.27.0](https://github.com/sasjs/server/compare/v0.26.2...v0.27.0) (2022-11-17)

### Features

* on startup add webout.sas file in sasautos folder ([200f6c5](200f6c596a))
2022-11-17 13:21:44 +00:00
Allan Bowe
b5f0911858 Merge pull request #321 from sasjs/issue-318
feat: on startup add webout.sas file in sasautos folder
2022-11-17 13:17:35 +00:00
b86ba5b8a3 chore: lint fix 2022-11-17 17:49:00 +05:00
200f6c596a feat: on startup add webout.sas file in sasautos folder 2022-11-17 17:03:23 +05:00
semantic-release-bot
1b7ccda6e9 chore(release): 0.26.2 [skip ci]
## [0.26.2](https://github.com/sasjs/server/compare/v0.26.1...v0.26.2) (2022-11-15)

### Bug Fixes

* comments ([7ae862c](7ae862c5ce))
2022-11-15 13:06:36 +00:00
Allan Bowe
532035d835 Merge pull request #317 from sasjs/docfix
fix: comments
2022-11-15 13:01:45 +00:00
Allan Bowe
7ae862c5ce fix: comments 2022-11-15 13:01:13 +00:00
semantic-release-bot
ab5858b8af chore(release): 0.26.1 [skip ci]
## [0.26.1](https://github.com/sasjs/server/compare/v0.26.0...v0.26.1) (2022-11-15)

### Bug Fixes

* change the expiration of access/refresh tokens from days to seconds ([bb05493](bb054938c5))
2022-11-15 12:31:03 +00:00
Allan Bowe
a39f5dd9f1 Merge pull request #316 from sasjs/access-token-expiration
fix: change the expiration of access/refresh tokens from days to seconds
2022-11-15 12:25:41 +00:00
Allan Bowe
3ea444756c Update Client.ts 2022-11-15 11:00:42 +00:00
Allan Bowe
96399ecbbe Update swagger.yaml 2022-11-15 10:54:52 +00:00
bb054938c5 fix: change the expiration of access/refresh tokens from days to seconds 2022-11-15 15:48:03 +05:00
semantic-release-bot
fb6a556630 chore(release): 0.26.0 [skip ci]
# [0.26.0](https://github.com/sasjs/server/compare/v0.25.1...v0.26.0) (2022-11-13)

### Bug Fixes

* **web:** dispose monaco editor actions in return of useEffect ([acc25cb](acc25cbd68))

### Features

* make access token duration configurable when creating client/secret ([2413c05](2413c05fea))
* make refresh token duration configurable ([abd5c64](abd5c64b4a))
2022-11-13 14:04:03 +00:00
Allan Bowe
9dbd8e16bd Merge pull request #315 from sasjs/issue-307
feat: make access token duration configurable when creating client
2022-11-13 14:00:03 +00:00
fe07c41f5f chore: update header 2022-11-11 15:35:24 +05:00
acc25cbd68 fix(web): dispose monaco editor actions in return of useEffect 2022-11-11 15:27:12 +05:00
4ca61feda6 chore: npm audit fix 2022-11-10 21:05:41 +05:00
abd5c64b4a feat: make refresh token duration configurable 2022-11-10 21:02:20 +05:00
2413c05fea feat: make access token duration configurable when creating client/secret 2022-11-10 19:43:06 +05:00
14 changed files with 177 additions and 35 deletions

View File

@@ -1,3 +1,37 @@
# [0.27.0](https://github.com/sasjs/server/compare/v0.26.2...v0.27.0) (2022-11-17)
### Features
* on startup add webout.sas file in sasautos folder ([200f6c5](https://github.com/sasjs/server/commit/200f6c596a6e732d799ed408f1f0fd92f216ba58))
## [0.26.2](https://github.com/sasjs/server/compare/v0.26.1...v0.26.2) (2022-11-15)
### Bug Fixes
* comments ([7ae862c](https://github.com/sasjs/server/commit/7ae862c5ce720e9483d4728f4295dede4f849436))
## [0.26.1](https://github.com/sasjs/server/compare/v0.26.0...v0.26.1) (2022-11-15)
### Bug Fixes
* change the expiration of access/refresh tokens from days to seconds ([bb05493](https://github.com/sasjs/server/commit/bb054938c5bd0535ae6b9da93ba0b14f9b80ddcd))
# [0.26.0](https://github.com/sasjs/server/compare/v0.25.1...v0.26.0) (2022-11-13)
### Bug Fixes
* **web:** dispose monaco editor actions in return of useEffect ([acc25cb](https://github.com/sasjs/server/commit/acc25cbd686952d3f1c65e57aefcebe1cb859cc7))
### Features
* make access token duration configurable when creating client/secret ([2413c05](https://github.com/sasjs/server/commit/2413c05fea3960f7e5c3c8b7b2f85d61314f08db))
* make refresh token duration configurable ([abd5c64](https://github.com/sasjs/server/commit/abd5c64b4a726e3f17594a98111b6aa269b71fee))
## [0.25.1](https://github.com/sasjs/server/compare/v0.25.0...v0.25.1) (2022-11-07)

12
api/package-lock.json generated
View File

@@ -7092,9 +7092,9 @@
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
},
"node_modules/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -15592,9 +15592,9 @@
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"requires": {
"brace-expansion": "^1.1.7"
}

View File

@@ -57,6 +57,16 @@ components:
type: string
description: 'Client Secret'
example: someRandomCryptoString
accessTokenExpiration:
type: number
format: double
description: 'Number of seconds after which access token will expire. Default is 86400 (1 day)'
example: 86400
refreshTokenExpiration:
type: number
format: double
description: 'Number of seconds after which access token will expire. Default is 2592000 (30 days)'
example: 2592000
required:
- clientId
- clientSecret
@@ -679,8 +689,8 @@ paths:
$ref: '#/components/schemas/ClientPayload'
examples:
'Example 1':
value: {clientId: someFormattedClientID1234, clientSecret: someRandomCryptoString}
summary: 'Create client with the following attributes: ClientId, ClientSecret. Admin only task.'
value: {clientId: someFormattedClientID1234, clientSecret: someRandomCryptoString, accessTokenExpiration: 86400}
summary: "Admin only task. Create client with the following attributes:\nClientId,\nClientSecret,\naccessTokenExpiration (optional),\nrefreshTokenExpiration (optional)"
tags:
- Client
security:

View File

@@ -5,12 +5,16 @@ import dotenv from 'dotenv'
import {
copySASjsCore,
createWeboutSasFile,
getFilesFolder,
getPackagesFolder,
getWebBuildFolder,
instantiateLogger,
loadAppStreamConfig,
ReturnCode,
setProcessVariables,
setupFolders,
setupFilesFolder,
setupPackagesFolder,
setupUserAutoExec,
verifyEnvVariables
} from './utils'
@@ -20,6 +24,7 @@ import {
configureLogger,
configureSecurity
} from './app-modules'
import { folderExists } from '@sasjs/utils'
dotenv.config()
@@ -65,9 +70,18 @@ export default setProcessVariables().then(async () => {
await setupUserAutoExec()
if (process.driveLoc === path.join(process.sasjsRoot, 'drive')) {
await setupFolders()
if (!(await folderExists(getFilesFolder()))) await setupFilesFolder()
if (!(await folderExists(getPackagesFolder()))) await setupPackagesFolder()
const sasautosPath = path.join(process.driveLoc, 'sas', 'sasautos')
if (await folderExists(sasautosPath)) {
console.log(
`SASAUTOS was not refreshed. To force a refresh, delete the ${sasautosPath} folder`
)
} else {
await copySASjsCore()
await createWeboutSasFile()
}
// loading these modules after setting up variables due to

View File

@@ -8,6 +8,7 @@ import {
removeTokensInDB,
saveTokensInDB
} from '../utils'
import Client from '../model/Client'
@Route('SASjsApi/auth')
@Tags('Auth')
@@ -83,8 +84,17 @@ const token = async (data: any): Promise<TokenResponse> => {
}
}
const accessToken = generateAccessToken(userInfo)
const refreshToken = generateRefreshToken(userInfo)
const client = await Client.findOne({ clientId })
if (!client) throw new Error('Invalid clientId.')
const accessToken = generateAccessToken(
userInfo,
client.accessTokenExpiration
)
const refreshToken = generateRefreshToken(
userInfo,
client.refreshTokenExpiration
)
await saveTokensInDB(userInfo.userId, clientId, accessToken, refreshToken)
@@ -92,8 +102,17 @@ const token = async (data: any): Promise<TokenResponse> => {
}
const refresh = async (userInfo: InfoJWT): Promise<TokenResponse> => {
const accessToken = generateAccessToken(userInfo)
const refreshToken = generateRefreshToken(userInfo)
const client = await Client.findOne({ clientId: userInfo.clientId })
if (!client) throw new Error('Invalid clientId.')
const accessToken = generateAccessToken(
userInfo,
client.accessTokenExpiration
)
const refreshToken = generateRefreshToken(
userInfo,
client.refreshTokenExpiration
)
await saveTokensInDB(
userInfo.userId,

View File

@@ -1,18 +1,27 @@
import { Security, Route, Tags, Example, Post, Body } from 'tsoa'
import Client, { ClientPayload } from '../model/Client'
import Client, {
ClientPayload,
NUMBER_OF_SECONDS_IN_A_DAY
} from '../model/Client'
@Security('bearerAuth')
@Route('SASjsApi/client')
@Tags('Client')
export class ClientController {
/**
* @summary Create client with the following attributes: ClientId, ClientSecret. Admin only task.
* @summary Admin only task. Create client with the following attributes:
* ClientId,
* ClientSecret,
* accessTokenExpiration (optional),
* refreshTokenExpiration (optional)
*
*/
@Example<ClientPayload>({
clientId: 'someFormattedClientID1234',
clientSecret: 'someRandomCryptoString'
clientSecret: 'someRandomCryptoString',
accessTokenExpiration: NUMBER_OF_SECONDS_IN_A_DAY,
refreshTokenExpiration: NUMBER_OF_SECONDS_IN_A_DAY * 30
})
@Post('/')
public async createClient(
@@ -22,8 +31,13 @@ export class ClientController {
}
}
const createClient = async (data: any): Promise<ClientPayload> => {
const { clientId, clientSecret } = data
const createClient = async (data: ClientPayload): Promise<ClientPayload> => {
const {
clientId,
clientSecret,
accessTokenExpiration,
refreshTokenExpiration
} = data
// Checking if client is already in the database
const clientExist = await Client.findOne({ clientId })
@@ -32,13 +46,17 @@ const createClient = async (data: any): Promise<ClientPayload> => {
// Create a new client
const client = new Client({
clientId,
clientSecret
clientSecret,
accessTokenExpiration,
refreshTokenExpiration
})
const savedClient = await client.save()
return {
clientId: savedClient.clientId,
clientSecret: savedClient.clientSecret
clientSecret: savedClient.clientSecret,
accessTokenExpiration: savedClient.accessTokenExpiration,
refreshTokenExpiration: savedClient.refreshTokenExpiration
}
}

View File

@@ -1,5 +1,6 @@
import mongoose, { Schema } from 'mongoose'
export const NUMBER_OF_SECONDS_IN_A_DAY = 86400
export interface ClientPayload {
/**
* Client ID
@@ -11,6 +12,16 @@ export interface ClientPayload {
* @example "someRandomCryptoString"
*/
clientSecret: string
/**
* Number of seconds after which access token will expire. Default is 86400 (1 day)
* @example 86400
*/
accessTokenExpiration?: number
/**
* Number of seconds after which access token will expire. Default is 2592000 (30 days)
* @example 2592000
*/
refreshTokenExpiration?: number
}
const ClientSchema = new Schema<ClientPayload>({
@@ -21,6 +32,14 @@ const ClientSchema = new Schema<ClientPayload>({
clientSecret: {
type: String,
required: true
},
accessTokenExpiration: {
type: Number,
default: NUMBER_OF_SECONDS_IN_A_DAY
},
refreshTokenExpiration: {
type: Number,
default: NUMBER_OF_SECONDS_IN_A_DAY * 30
}
})

View File

@@ -0,0 +1,18 @@
import path from 'path'
import { createFile } from '@sasjs/utils'
import { getMacrosFolder } from './file'
const fileContent = `%macro webout(action,ds,dslabel=,fmt=,missing=NULL,showmeta=NO,maxobs=MAX);
%ms_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt
,missing=&missing
,showmeta=&showmeta
,maxobs=&maxobs
)
%mend;`
export const createWeboutSasFile = async () => {
const macrosDrivePath = getMacrosFolder()
console.log(`Creating webout.sas at ${macrosDrivePath}`)
const filePath = path.join(macrosDrivePath, 'webout.sas')
await createFile(filePath, fileContent)
}

View File

@@ -1,7 +1,8 @@
import jwt from 'jsonwebtoken'
import { InfoJWT } from '../types'
import { NUMBER_OF_SECONDS_IN_A_DAY } from '../model/Client'
export const generateAccessToken = (data: InfoJWT) =>
export const generateAccessToken = (data: InfoJWT, expiry?: number) =>
jwt.sign(data, process.secrets.ACCESS_TOKEN_SECRET, {
expiresIn: '1day'
expiresIn: expiry ? expiry : NUMBER_OF_SECONDS_IN_A_DAY
})

View File

@@ -1,7 +1,8 @@
import jwt from 'jsonwebtoken'
import { InfoJWT } from '../types'
import { NUMBER_OF_SECONDS_IN_A_DAY } from '../model/Client'
export const generateRefreshToken = (data: InfoJWT) =>
export const generateRefreshToken = (data: InfoJWT, expiry?: number) =>
jwt.sign(data, process.secrets.REFRESH_TOKEN_SECRET, {
expiresIn: '30 days'
expiresIn: expiry ? expiry : NUMBER_OF_SECONDS_IN_A_DAY
})

View File

@@ -1,6 +1,7 @@
export * from './appStreamConfig'
export * from './connectDB'
export * from './copySASjsCore'
export * from './createWeboutSasFile'
export * from './desktopAutoExec'
export * from './extractHeaders'
export * from './extractName'

View File

@@ -1,7 +1,7 @@
import { createFolder } from '@sasjs/utils'
import { getFilesFolder, getPackagesFolder } from './file'
export const setupFolders = async () => {
await createFolder(getFilesFolder())
export const setupFilesFolder = async () => await createFolder(getFilesFolder())
export const setupPackagesFolder = async () =>
await createFolder(getPackagesFolder())
}

View File

@@ -88,7 +88,9 @@ export const updateUserValidation = (
export const registerClientValidation = (data: any): Joi.ValidationResult =>
Joi.object({
clientId: Joi.string().required(),
clientSecret: Joi.string().required()
clientSecret: Joi.string().required(),
accessTokenExpiration: Joi.number(),
refreshTokenExpiration: Joi.number()
}).validate(data)
export const registerPermissionValidation = (data: any): Joi.ValidationResult =>

View File

@@ -49,7 +49,7 @@ const useEditor = ({
const [openFilePathInputModal, setOpenFilePathInputModal] = useState(false)
const [showDiff, setShowDiff] = useState(false)
const editorRef = useRef(null as any)
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)
const handleEditorDidMount: EditorDidMount = (editor) => {
editorRef.current = editor
@@ -199,7 +199,7 @@ const useEditor = ({
}
useEffect(() => {
editorRef.current.addAction({
const saveFileAction = editorRef.current?.addAction({
// An unique identifier of the contributed action.
id: 'save-file',
@@ -209,6 +209,8 @@ const useEditor = ({
// An optional array of keybindings for the action.
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS],
contextMenuGroupId: '9_cutcopypaste',
// Method that will be executed when the action is triggered.
// @param editor The editor instance is passed in as a convenience
run: () => {
@@ -217,7 +219,7 @@ const useEditor = ({
}
})
editorRef.current.addAction({
const runCodeAction = editorRef.current?.addAction({
// An unique identifier of the contributed action.
id: 'run-code',
@@ -229,14 +231,17 @@ const useEditor = ({
contextMenuGroupId: 'navigation',
contextMenuOrder: 1,
// Method that will be executed when the action is triggered.
// @param editor The editor instance is passed in as a convenience
run: function () {
runCode(getSelection(editorRef.current as any) || fileContent)
}
})
return () => {
saveFileAction?.dispose()
runCodeAction?.dispose()
}
}, [fileContent, prevFileContent, selectedFilePath, saveFile, runCode])
useEffect(() => {