mirror of
https://github.com/sasjs/server.git
synced 2026-01-13 09:00:04 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b666d5554 | ||
|
|
b5f0911858 | ||
| b86ba5b8a3 | |||
| 200f6c596a | |||
|
|
1b7ccda6e9 | ||
|
|
532035d835 | ||
|
|
7ae862c5ce | ||
|
|
ab5858b8af | ||
|
|
a39f5dd9f1 | ||
|
|
3ea444756c | ||
|
|
96399ecbbe | ||
| bb054938c5 |
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,3 +1,24 @@
|
|||||||
|
# [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)
|
# [0.26.0](https://github.com/sasjs/server/compare/v0.25.1...v0.26.0) (2022-11-13)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -57,16 +57,16 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
description: 'Client Secret'
|
description: 'Client Secret'
|
||||||
example: someRandomCryptoString
|
example: someRandomCryptoString
|
||||||
accessTokenExpiryDays:
|
accessTokenExpiration:
|
||||||
type: number
|
type: number
|
||||||
format: double
|
format: double
|
||||||
description: 'Number of days in which access token will expire'
|
description: 'Number of seconds after which access token will expire. Default is 86400 (1 day)'
|
||||||
example: 1
|
example: 86400
|
||||||
refreshTokenExpiryDays:
|
refreshTokenExpiration:
|
||||||
type: number
|
type: number
|
||||||
format: double
|
format: double
|
||||||
description: 'Number of days in which access token will expire'
|
description: 'Number of seconds after which access token will expire. Default is 2592000 (30 days)'
|
||||||
example: 30
|
example: 2592000
|
||||||
required:
|
required:
|
||||||
- clientId
|
- clientId
|
||||||
- clientSecret
|
- clientSecret
|
||||||
@@ -689,8 +689,8 @@ paths:
|
|||||||
$ref: '#/components/schemas/ClientPayload'
|
$ref: '#/components/schemas/ClientPayload'
|
||||||
examples:
|
examples:
|
||||||
'Example 1':
|
'Example 1':
|
||||||
value: {clientId: someFormattedClientID1234, clientSecret: someRandomCryptoString, accessTokenExpiryDays: 1, refreshTokenExpiryDays: 30}
|
value: {clientId: someFormattedClientID1234, clientSecret: someRandomCryptoString, accessTokenExpiration: 86400}
|
||||||
summary: "Admin only task. Create client with the following attributes:\nClientId,\nClientSecret,\naccessTokenExpiryDays (optional),\nrefreshTokenExpiryDays (optional)"
|
summary: "Admin only task. Create client with the following attributes:\nClientId,\nClientSecret,\naccessTokenExpiration (optional),\nrefreshTokenExpiration (optional)"
|
||||||
tags:
|
tags:
|
||||||
- Client
|
- Client
|
||||||
security:
|
security:
|
||||||
|
|||||||
@@ -5,12 +5,16 @@ import dotenv from 'dotenv'
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
copySASjsCore,
|
copySASjsCore,
|
||||||
|
createWeboutSasFile,
|
||||||
|
getFilesFolder,
|
||||||
|
getPackagesFolder,
|
||||||
getWebBuildFolder,
|
getWebBuildFolder,
|
||||||
instantiateLogger,
|
instantiateLogger,
|
||||||
loadAppStreamConfig,
|
loadAppStreamConfig,
|
||||||
ReturnCode,
|
ReturnCode,
|
||||||
setProcessVariables,
|
setProcessVariables,
|
||||||
setupFolders,
|
setupFilesFolder,
|
||||||
|
setupPackagesFolder,
|
||||||
setupUserAutoExec,
|
setupUserAutoExec,
|
||||||
verifyEnvVariables
|
verifyEnvVariables
|
||||||
} from './utils'
|
} from './utils'
|
||||||
@@ -20,6 +24,7 @@ import {
|
|||||||
configureLogger,
|
configureLogger,
|
||||||
configureSecurity
|
configureSecurity
|
||||||
} from './app-modules'
|
} from './app-modules'
|
||||||
|
import { folderExists } from '@sasjs/utils'
|
||||||
|
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
|
|
||||||
@@ -65,9 +70,18 @@ export default setProcessVariables().then(async () => {
|
|||||||
|
|
||||||
await setupUserAutoExec()
|
await setupUserAutoExec()
|
||||||
|
|
||||||
if (process.driveLoc === path.join(process.sasjsRoot, 'drive')) {
|
if (!(await folderExists(getFilesFolder()))) await setupFilesFolder()
|
||||||
await setupFolders()
|
|
||||||
|
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 copySASjsCore()
|
||||||
|
await createWeboutSasFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
// loading these modules after setting up variables due to
|
// loading these modules after setting up variables due to
|
||||||
|
|||||||
@@ -89,11 +89,11 @@ const token = async (data: any): Promise<TokenResponse> => {
|
|||||||
|
|
||||||
const accessToken = generateAccessToken(
|
const accessToken = generateAccessToken(
|
||||||
userInfo,
|
userInfo,
|
||||||
client.accessTokenExpiryDays
|
client.accessTokenExpiration
|
||||||
)
|
)
|
||||||
const refreshToken = generateRefreshToken(
|
const refreshToken = generateRefreshToken(
|
||||||
userInfo,
|
userInfo,
|
||||||
client.refreshTokenExpiryDays
|
client.refreshTokenExpiration
|
||||||
)
|
)
|
||||||
|
|
||||||
await saveTokensInDB(userInfo.userId, clientId, accessToken, refreshToken)
|
await saveTokensInDB(userInfo.userId, clientId, accessToken, refreshToken)
|
||||||
@@ -107,11 +107,11 @@ const refresh = async (userInfo: InfoJWT): Promise<TokenResponse> => {
|
|||||||
|
|
||||||
const accessToken = generateAccessToken(
|
const accessToken = generateAccessToken(
|
||||||
userInfo,
|
userInfo,
|
||||||
client.accessTokenExpiryDays
|
client.accessTokenExpiration
|
||||||
)
|
)
|
||||||
const refreshToken = generateRefreshToken(
|
const refreshToken = generateRefreshToken(
|
||||||
userInfo,
|
userInfo,
|
||||||
client.refreshTokenExpiryDays
|
client.refreshTokenExpiration
|
||||||
)
|
)
|
||||||
|
|
||||||
await saveTokensInDB(
|
await saveTokensInDB(
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { Security, Route, Tags, Example, Post, Body } from 'tsoa'
|
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')
|
@Security('bearerAuth')
|
||||||
@Route('SASjsApi/client')
|
@Route('SASjsApi/client')
|
||||||
@@ -10,15 +13,15 @@ export class ClientController {
|
|||||||
* @summary Admin only task. Create client with the following attributes:
|
* @summary Admin only task. Create client with the following attributes:
|
||||||
* ClientId,
|
* ClientId,
|
||||||
* ClientSecret,
|
* ClientSecret,
|
||||||
* accessTokenExpiryDays (optional),
|
* accessTokenExpiration (optional),
|
||||||
* refreshTokenExpiryDays (optional)
|
* refreshTokenExpiration (optional)
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@Example<ClientPayload>({
|
@Example<ClientPayload>({
|
||||||
clientId: 'someFormattedClientID1234',
|
clientId: 'someFormattedClientID1234',
|
||||||
clientSecret: 'someRandomCryptoString',
|
clientSecret: 'someRandomCryptoString',
|
||||||
accessTokenExpiryDays: 1,
|
accessTokenExpiration: NUMBER_OF_SECONDS_IN_A_DAY,
|
||||||
refreshTokenExpiryDays: 30
|
refreshTokenExpiration: NUMBER_OF_SECONDS_IN_A_DAY * 30
|
||||||
})
|
})
|
||||||
@Post('/')
|
@Post('/')
|
||||||
public async createClient(
|
public async createClient(
|
||||||
@@ -32,8 +35,8 @@ const createClient = async (data: ClientPayload): Promise<ClientPayload> => {
|
|||||||
const {
|
const {
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
accessTokenExpiryDays,
|
accessTokenExpiration,
|
||||||
refreshTokenExpiryDays
|
refreshTokenExpiration
|
||||||
} = data
|
} = data
|
||||||
|
|
||||||
// Checking if client is already in the database
|
// Checking if client is already in the database
|
||||||
@@ -44,7 +47,8 @@ const createClient = async (data: ClientPayload): Promise<ClientPayload> => {
|
|||||||
const client = new Client({
|
const client = new Client({
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
accessTokenExpiryDays
|
accessTokenExpiration,
|
||||||
|
refreshTokenExpiration
|
||||||
})
|
})
|
||||||
|
|
||||||
const savedClient = await client.save()
|
const savedClient = await client.save()
|
||||||
@@ -52,7 +56,7 @@ const createClient = async (data: ClientPayload): Promise<ClientPayload> => {
|
|||||||
return {
|
return {
|
||||||
clientId: savedClient.clientId,
|
clientId: savedClient.clientId,
|
||||||
clientSecret: savedClient.clientSecret,
|
clientSecret: savedClient.clientSecret,
|
||||||
accessTokenExpiryDays: savedClient.accessTokenExpiryDays,
|
accessTokenExpiration: savedClient.accessTokenExpiration,
|
||||||
refreshTokenExpiryDays: savedClient.refreshTokenExpiryDays
|
refreshTokenExpiration: savedClient.refreshTokenExpiration
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import mongoose, { Schema } from 'mongoose'
|
import mongoose, { Schema } from 'mongoose'
|
||||||
|
|
||||||
|
export const NUMBER_OF_SECONDS_IN_A_DAY = 86400
|
||||||
export interface ClientPayload {
|
export interface ClientPayload {
|
||||||
/**
|
/**
|
||||||
* Client ID
|
* Client ID
|
||||||
@@ -12,15 +13,15 @@ export interface ClientPayload {
|
|||||||
*/
|
*/
|
||||||
clientSecret: string
|
clientSecret: string
|
||||||
/**
|
/**
|
||||||
* Number of days in which access token will expire
|
* Number of seconds after which access token will expire. Default is 86400 (1 day)
|
||||||
* @example 1
|
* @example 86400
|
||||||
*/
|
*/
|
||||||
accessTokenExpiryDays?: number
|
accessTokenExpiration?: number
|
||||||
/**
|
/**
|
||||||
* Number of days in which access token will expire
|
* Number of seconds after which access token will expire. Default is 2592000 (30 days)
|
||||||
* @example 30
|
* @example 2592000
|
||||||
*/
|
*/
|
||||||
refreshTokenExpiryDays?: number
|
refreshTokenExpiration?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const ClientSchema = new Schema<ClientPayload>({
|
const ClientSchema = new Schema<ClientPayload>({
|
||||||
@@ -32,13 +33,13 @@ const ClientSchema = new Schema<ClientPayload>({
|
|||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
accessTokenExpiryDays: {
|
accessTokenExpiration: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 1
|
default: NUMBER_OF_SECONDS_IN_A_DAY
|
||||||
},
|
},
|
||||||
refreshTokenExpiryDays: {
|
refreshTokenExpiration: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 30
|
default: NUMBER_OF_SECONDS_IN_A_DAY * 30
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
18
api/src/utils/createWeboutSasFile.ts
Normal file
18
api/src/utils/createWeboutSasFile.ts
Normal 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)
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import jwt from 'jsonwebtoken'
|
import jwt from 'jsonwebtoken'
|
||||||
import { InfoJWT } from '../types'
|
import { InfoJWT } from '../types'
|
||||||
|
import { NUMBER_OF_SECONDS_IN_A_DAY } from '../model/Client'
|
||||||
|
|
||||||
export const generateAccessToken = (data: InfoJWT, expiry?: number) =>
|
export const generateAccessToken = (data: InfoJWT, expiry?: number) =>
|
||||||
jwt.sign(data, process.secrets.ACCESS_TOKEN_SECRET, {
|
jwt.sign(data, process.secrets.ACCESS_TOKEN_SECRET, {
|
||||||
expiresIn: expiry ? `${expiry}d` : '1d'
|
expiresIn: expiry ? expiry : NUMBER_OF_SECONDS_IN_A_DAY
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import jwt from 'jsonwebtoken'
|
import jwt from 'jsonwebtoken'
|
||||||
import { InfoJWT } from '../types'
|
import { InfoJWT } from '../types'
|
||||||
|
import { NUMBER_OF_SECONDS_IN_A_DAY } from '../model/Client'
|
||||||
|
|
||||||
export const generateRefreshToken = (data: InfoJWT, expiry?: number) =>
|
export const generateRefreshToken = (data: InfoJWT, expiry?: number) =>
|
||||||
jwt.sign(data, process.secrets.REFRESH_TOKEN_SECRET, {
|
jwt.sign(data, process.secrets.REFRESH_TOKEN_SECRET, {
|
||||||
expiresIn: expiry ? `${expiry}d` : '30d'
|
expiresIn: expiry ? expiry : NUMBER_OF_SECONDS_IN_A_DAY
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export * from './appStreamConfig'
|
export * from './appStreamConfig'
|
||||||
export * from './connectDB'
|
export * from './connectDB'
|
||||||
export * from './copySASjsCore'
|
export * from './copySASjsCore'
|
||||||
|
export * from './createWeboutSasFile'
|
||||||
export * from './desktopAutoExec'
|
export * from './desktopAutoExec'
|
||||||
export * from './extractHeaders'
|
export * from './extractHeaders'
|
||||||
export * from './extractName'
|
export * from './extractName'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createFolder } from '@sasjs/utils'
|
import { createFolder } from '@sasjs/utils'
|
||||||
import { getFilesFolder, getPackagesFolder } from './file'
|
import { getFilesFolder, getPackagesFolder } from './file'
|
||||||
|
|
||||||
export const setupFolders = async () => {
|
export const setupFilesFolder = async () => await createFolder(getFilesFolder())
|
||||||
await createFolder(getFilesFolder())
|
|
||||||
|
export const setupPackagesFolder = async () =>
|
||||||
await createFolder(getPackagesFolder())
|
await createFolder(getPackagesFolder())
|
||||||
}
|
|
||||||
|
|||||||
@@ -89,8 +89,8 @@ export const registerClientValidation = (data: any): Joi.ValidationResult =>
|
|||||||
Joi.object({
|
Joi.object({
|
||||||
clientId: Joi.string().required(),
|
clientId: Joi.string().required(),
|
||||||
clientSecret: Joi.string().required(),
|
clientSecret: Joi.string().required(),
|
||||||
accessTokenExpiryDays: Joi.number(),
|
accessTokenExpiration: Joi.number(),
|
||||||
refreshTokenExpiryDays: Joi.number()
|
refreshTokenExpiration: Joi.number()
|
||||||
}).validate(data)
|
}).validate(data)
|
||||||
|
|
||||||
export const registerPermissionValidation = (data: any): Joi.ValidationResult =>
|
export const registerPermissionValidation = (data: any): Joi.ValidationResult =>
|
||||||
|
|||||||
Reference in New Issue
Block a user