diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3a86a0f..e93774d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,6 +54,7 @@ jobs: ACCESS_TOKEN_SECRET: ${{secrets.ACCESS_TOKEN_SECRET}} REFRESH_TOKEN_SECRET: ${{secrets.REFRESH_TOKEN_SECRET}} AUTH_CODE_SECRET: ${{secrets.AUTH_CODE_SECRET}} + SESSION_SECRET: ${{secrets.SESSION_SECRET}} - name: Build Package working-directory: ./api diff --git a/api/public/swagger.yaml b/api/public/swagger.yaml index ed3b794..923115f 100644 --- a/api/public/swagger.yaml +++ b/api/public/swagger.yaml @@ -5,51 +5,6 @@ components: requestBodies: {} responses: {} schemas: - 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: - username: - type: string - description: 'Username for user' - example: secretuser - password: - type: string - description: 'Password for user' - example: secretpassword - clientId: - type: string - description: 'Client ID' - example: clientID1 - required: - - username - - password - - clientId - type: object - additionalProperties: false TokenResponse: properties: accessToken: @@ -92,6 +47,41 @@ components: - userId type: object 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: properties: clientId: @@ -425,14 +415,6 @@ components: - description type: object additionalProperties: false - ExecuteReturnJsonPayload: - properties: - _program: - type: string - description: 'Location of SAS program' - example: /Public/somefolder/some.file - type: object - additionalProperties: false InfoResponse: properties: mode: @@ -452,6 +434,14 @@ components: - protocol type: object additionalProperties: false + ExecuteReturnJsonPayload: + properties: + _program: + type: string + description: 'Location of SAS program' + example: /Public/somefolder/some.file + type: object + additionalProperties: false securitySchemes: bearerAuth: type: http @@ -465,86 +455,6 @@ info: name: '4GL Ltd' openapi: 3.0.0 paths: - /: - get: - operationId: Home - responses: - '200': - description: Ok - content: - application/json: - schema: - type: string - summary: 'Render index.html' - tags: - - Web - security: [] - parameters: [] - /login: - post: - operationId: Login - responses: - '200': - description: Ok - content: - application/json: - schema: - properties: - user: {properties: {displayName: {type: string}, username: {type: string}}, required: [displayName, username], 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' - /logout: - get: - operationId: Logout - responses: - '200': - description: Ok - content: - application/json: - schema: {} - summary: 'Accept a valid username/password' - tags: - - Web - security: [] - parameters: [] - /SASjsApi/auth/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: - - Auth - security: [] - parameters: [] - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AuthorizePayload' /SASjsApi/auth/token: post: operationId: Token @@ -602,6 +512,86 @@ paths: - bearerAuth: [] 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}}, required: [displayName, username], 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' + /logout: + get: + operationId: Logout + responses: + '200': + description: Ok + content: + application/json: + schema: {} + summary: 'Accept a valid username/password' + tags: + - Web + security: [] + parameters: [] /SASjsApi/client: post: operationId: CreateClient @@ -1248,6 +1238,24 @@ paths: format: double type: number example: '6789' + /SASjsApi/info: + get: + operationId: Info + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/InfoResponse' + examples: + 'Example 1': + value: {mode: desktop, cors: enable, whiteList: ['http://example.com', 'http://example2.com'], protocol: http} + summary: 'Get server info (mode, cors, whiteList, protocol).' + tags: + - Info + security: [] + parameters: [] /SASjsApi/session: get: operationId: Session @@ -1330,24 +1338,6 @@ paths: application/json: schema: $ref: '#/components/schemas/ExecuteReturnJsonPayload' - /SASjsApi/info: - get: - operationId: Info - responses: - '200': - description: Ok - content: - application/json: - schema: - $ref: '#/components/schemas/InfoResponse' - examples: - 'Example 1': - value: {mode: desktop, cors: enable, whiteList: ['http://example.com', 'http://example2.com'], protocol: http} - summary: 'Get server info (mode, cors, whiteList, protocol).' - tags: - - Info - security: [] - parameters: [] servers: - url: / diff --git a/api/src/app.ts b/api/src/app.ts index a41895c..ce828dd 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -85,21 +85,25 @@ if (MODE?.trim() !== 'server' || CORS?.trim() === 'enable') { * With Mongo Store * ***********************************/ if (MODE?.trim() === 'server') { + let store: MongoStore | undefined + // NOTE: when exporting app.js as agent for supertest // we should exclude connecting to the real database if (process.env.NODE_ENV !== 'test') { const clientPromise = connectDB().then((conn) => conn!.getClient() as any) - app.use( - session({ - 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: MongoStore.create({ clientPromise, collectionName: 'sessions' }), - cookie: cookieOptions - }) - ) + store = MongoStore.create({ clientPromise, collectionName: 'sessions' }) } + + app.use( + session({ + 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' })) app.use(express.static(path.join(__dirname, '../public'))) diff --git a/api/src/controllers/auth.ts b/api/src/controllers/auth.ts index dba3db9..e642213 100644 --- a/api/src/controllers/auth.ts +++ b/api/src/controllers/auth.ts @@ -1,11 +1,8 @@ import { Security, Route, Tags, Example, Post, Body, Query, Hidden } from 'tsoa' import jwt from 'jsonwebtoken' -import User from '../model/User' -import Client from '../model/Client' import { InfoJWT } from '../types' import { generateAccessToken, - generateAuthCode, generateRefreshToken, removeTokensInDB, saveTokensInDB @@ -25,20 +22,6 @@ export class AuthController { static deleteCode = (userId: number, clientId: string) => delete AuthController.authCodes[userId][clientId] - /** - * @summary Accept a valid username/password, plus a CLIENT_ID, and return an AUTH_CODE - * - */ - @Example({ - code: 'someRandomCryptoString' - }) - @Post('/authorize') - public async authorize( - @Body() body: AuthorizePayload - ): Promise { - return authorize(body) - } - /** * @summary Accepts client/auth code and returns access/refresh tokens * @@ -79,33 +62,6 @@ export class AuthController { } } -const authorize = async (data: any): Promise => { - const { username, password, clientId } = data - - const client = await Client.findOne({ clientId }) - if (!client) throw new Error('Invalid clientId.') - - // Authenticate User - const user = await User.findOne({ username }) - if (!user) throw new Error('Username is not found.') - - const validPass = user.comparePassword(password) - if (!validPass) throw new Error('Invalid password.') - - // generate authorization code against clientId - const userInfo: InfoJWT = { - clientId, - userId: user.id - } - const code = AuthController.saveCode( - user.id, - clientId, - generateAuthCode(userInfo) - ) - - return { code } -} - const token = async (data: any): Promise => { const { clientId, code } = data @@ -143,32 +99,6 @@ const logout = async (userInfo: InfoJWT) => { await removeTokensInDB(userInfo.userId, userInfo.clientId) } -interface AuthorizePayload { - /** - * Username for user - * @example "secretuser" - */ - username: string - /** - * Password for user - * @example "secretpassword" - */ - password: string - /** - * Client ID - * @example "clientID1" - */ - clientId: string -} - -interface AuthorizeResponse { - /** - * Authorization code - * @example "someRandomCryptoString" - */ - code: string -} - interface TokenPayload { /** * Client ID diff --git a/api/src/controllers/index.ts b/api/src/controllers/index.ts index 05e8b84..80bbbd1 100644 --- a/api/src/controllers/index.ts +++ b/api/src/controllers/index.ts @@ -3,7 +3,8 @@ export * from './client' export * from './code' export * from './drive' export * from './group' +export * from './info' export * from './session' export * from './stp' export * from './user' -export * from './info' +export * from './web' diff --git a/api/src/controllers/web.ts b/api/src/controllers/web.ts index 7826f57..70ccf6a 100644 --- a/api/src/controllers/web.ts +++ b/api/src/controllers/web.ts @@ -1,10 +1,13 @@ import path from 'path' import express from 'express' -import { Request, Route, Tags, Post, Body, Get } from 'tsoa' +import { Request, Route, Tags, Post, Body, Get, Example } from 'tsoa' import { readFile } from '@sasjs/utils' import User from '../model/User' -import { getWebBuildFolderPath } from '../utils' +import Client from '../model/Client' +import { getWebBuildFolderPath, generateAuthCode } from '../utils' +import { InfoJWT } from '../types' +import { AuthController } from './auth' @Route('/') @Tags('Web') @@ -22,7 +25,7 @@ export class WebController { * @summary Accept a valid username/password * */ - @Post('/login') + @Post('/SASLogon/login') public async login( @Request() req: express.Request, @Body() body: LoginPayload @@ -30,6 +33,21 @@ export class WebController { return login(req, body) } + /** + * @summary Accept a valid username/password, plus a CLIENT_ID, and return an AUTH_CODE + * + */ + @Example({ + code: 'someRandomCryptoString' + }) + @Post('/SASLogon/authorize') + public async authorize( + @Request() req: express.Request, + @Body() body: AuthorizePayload + ): Promise { + return authorize(req, body.clientId) + } + /** * @summary Accept a valid username/password * @@ -84,6 +102,30 @@ const login = async ( } } +const authorize = async ( + req: express.Request, + clientId: string +): Promise => { + const userId = req.session.user?.userId + if (!userId) throw new Error('Invalid userId.') + + const client = await Client.findOne({ clientId }) + if (!client) throw new Error('Invalid clientId.') + + // generate authorization code against clientId + const userInfo: InfoJWT = { + clientId, + userId + } + const code = AuthController.saveCode( + userId, + clientId, + generateAuthCode(userInfo) + ) + + return { code } +} + interface LoginPayload { /** * Username for user @@ -96,3 +138,19 @@ interface LoginPayload { */ password: string } + +interface AuthorizePayload { + /** + * Client ID + * @example "clientID1" + */ + clientId: string +} + +interface AuthorizeResponse { + /** + * Authorization code + * @example "someRandomCryptoString" + */ + code: string +} diff --git a/api/src/routes/api/auth.ts b/api/src/routes/api/auth.ts index 8965052..d463b22 100644 --- a/api/src/routes/api/auth.ts +++ b/api/src/routes/api/auth.ts @@ -13,19 +13,6 @@ import { InfoJWT } from '../../types' const authRouter = express.Router() const controller = new AuthController() -authRouter.post('/authorize', async (req, res) => { - const { error, value: body } = authorizeValidation(req.body) - if (error) return res.status(400).send(error.details[0].message) - - try { - const response = await controller.authorize(body) - - res.send(response) - } catch (err: any) { - res.status(403).send(err.toString()) - } -}) - authRouter.post('/token', async (req, res) => { const { error, value: body } = tokenValidation(req.body) if (error) return res.status(400).send(error.details[0].message) diff --git a/api/src/routes/api/spec/auth.spec.ts b/api/src/routes/api/spec/auth.spec.ts index 4a0014d..0e1e5b1 100644 --- a/api/src/routes/api/spec/auth.spec.ts +++ b/api/src/routes/api/spec/auth.spec.ts @@ -49,114 +49,6 @@ describe('auth', () => { await mongoServer.stop() }) - describe('authorize', () => { - afterEach(async () => { - const collections = mongoose.connection.collections - const collection = collections['users'] - await collection.deleteMany({}) - }) - - it('should respond with authorization code', async () => { - await userController.createUser(user) - - const res = await request(app) - .post('/SASjsApi/auth/authorize') - .send({ - username: user.username, - password: user.password, - clientId - }) - .expect(200) - - expect(res.body).toHaveProperty('code') - }) - - it('should respond with Bad Request if username is missing', async () => { - const res = await request(app) - .post('/SASjsApi/auth/authorize') - .send({ - password: user.password, - clientId - }) - .expect(400) - - expect(res.text).toEqual(`"username" is required`) - expect(res.body).toEqual({}) - }) - - it('should respond with Bad Request if password is missing', async () => { - const res = await request(app) - .post('/SASjsApi/auth/authorize') - .send({ - username: user.username, - clientId - }) - .expect(400) - - expect(res.text).toEqual(`"password" is required`) - expect(res.body).toEqual({}) - }) - - it('should respond with Bad Request if clientId is missing', async () => { - const res = await request(app) - .post('/SASjsApi/auth/authorize') - .send({ - username: user.username, - password: user.password - }) - .expect(400) - - expect(res.text).toEqual(`"clientId" is required`) - expect(res.body).toEqual({}) - }) - - it('should respond with Forbidden if username is incorrect', async () => { - const res = await request(app) - .post('/SASjsApi/auth/authorize') - .send({ - username: user.username, - password: user.password, - clientId - }) - .expect(403) - - expect(res.text).toEqual('Error: Username is not found.') - expect(res.body).toEqual({}) - }) - - it('should respond with Forbidden if password is incorrect', async () => { - await userController.createUser(user) - - const res = await request(app) - .post('/SASjsApi/auth/authorize') - .send({ - username: user.username, - password: 'WrongPassword', - clientId - }) - .expect(403) - - expect(res.text).toEqual('Error: Invalid password.') - expect(res.body).toEqual({}) - }) - - it('should respond with Forbidden if clientId is incorrect', async () => { - await userController.createUser(user) - - const res = await request(app) - .post('/SASjsApi/auth/authorize') - .send({ - username: user.username, - password: user.password, - clientId: 'WrongClientID' - }) - .expect(403) - - expect(res.text).toEqual('Error: Invalid clientId.') - expect(res.body).toEqual({}) - }) - }) - describe('token', () => { const userInfo: InfoJWT = { clientId, diff --git a/api/src/routes/api/spec/web.spec.ts b/api/src/routes/api/spec/web.spec.ts new file mode 100644 index 0000000..12da618 --- /dev/null +++ b/api/src/routes/api/spec/web.spec.ts @@ -0,0 +1,182 @@ +import { Express } from 'express' +import mongoose, { Mongoose } from 'mongoose' +import { MongoMemoryServer } from 'mongodb-memory-server' +import request from 'supertest' +import appPromise from '../../../app' +import { UserController, ClientController } from '../../../controllers/' + +const clientId = 'someclientID' +const clientSecret = 'someclientSecret' +const user = { + id: 1234, + displayName: 'Test User', + username: 'testUsername', + password: '87654321', + isAdmin: false, + isActive: true +} + +describe('web', () => { + let app: Express + let con: Mongoose + let mongoServer: MongoMemoryServer + const userController = new UserController() + const clientController = new ClientController() + + beforeAll(async () => { + app = await appPromise + + mongoServer = await MongoMemoryServer.create() + con = await mongoose.connect(mongoServer.getUri()) + await clientController.createClient({ clientId, clientSecret }) + }) + + afterAll(async () => { + await con.connection.dropDatabase() + await con.connection.close() + await mongoServer.stop() + }) + + describe('home', () => { + it('should respond with CSRF Token', async () => { + await request(app) + .get('/') + .expect( + 'set-cookie', + /_csrf=.*; Max-Age=86400000; Path=\/; HttpOnly,XSRF-TOKEN=.*; Path=\// + ) + }) + }) + + describe('SASLogon/login', () => { + let csrfToken: string + let cookies: string + + beforeAll(async () => { + ;({ csrfToken, cookies } = await getCSRF(app)) + }) + + afterEach(async () => { + const collections = mongoose.connection.collections + const collection = collections['users'] + await collection.deleteMany({}) + }) + + it('should respond with successful login', async () => { + await userController.createUser(user) + + const res = await request(app) + .post('/SASLogon/login') + .set('Cookie', cookies) + .set('x-xsrf-token', csrfToken) + .send({ + username: user.username, + password: user.password + }) + .expect(200) + + expect(res.body.loggedIn).toBeTruthy() + expect(res.body.user).toEqual({ + username: user.username, + displayName: user.displayName + }) + }) + }) + + describe('SASLogon/authorize', () => { + let csrfToken: string + let cookies: string + let authCookies: string + + beforeAll(async () => { + ;({ csrfToken, cookies } = await getCSRF(app)) + + await userController.createUser(user) + + const credentials = { + username: user.username, + password: user.password + } + + ;({ cookies: authCookies } = await performLogin( + app, + credentials, + cookies, + csrfToken + )) + }) + + afterAll(async () => { + const collections = mongoose.connection.collections + const collection = collections['users'] + await collection.deleteMany({}) + }) + + it('should respond with authorization code', async () => { + const res = await request(app) + .post('/SASLogon/authorize') + .set('Cookie', [authCookies, cookies].join('; ')) + .set('x-xsrf-token', csrfToken) + .send({ clientId }) + + expect(res.body).toHaveProperty('code') + }) + + it('should respond with Bad Request if clientId is missing', async () => { + const res = await request(app) + .post('/SASLogon/authorize') + .set('Cookie', [authCookies, cookies].join('; ')) + .set('x-xsrf-token', csrfToken) + .send({}) + .expect(400) + + expect(res.text).toEqual(`"clientId" is required`) + expect(res.body).toEqual({}) + }) + + it('should respond with Forbidden if clientId is incorrect', async () => { + const res = await request(app) + .post('/SASLogon/authorize') + .set('Cookie', [authCookies, cookies].join('; ')) + .set('x-xsrf-token', csrfToken) + .send({ + clientId: 'WrongClientID' + }) + .expect(403) + + expect(res.text).toEqual('Error: Invalid clientId.') + expect(res.body).toEqual({}) + }) + }) +}) + +const getCSRF = async (app: Express) => { + // make request to get CSRF + const { header } = await request(app).get('/') + const cookies = header['set-cookie'].join() + + console.log('cookies', cookies) + const csrfToken = extractCSRF(cookies) + return { csrfToken, cookies } +} + +const performLogin = async ( + app: Express, + credentials: { username: string; password: string }, + cookies: string, + csrfToken: string +) => { + const { header } = await request(app) + .post('/SASLogon/login') + .set('Cookie', cookies) + .set('x-xsrf-token', csrfToken) + .send(credentials) + + const newCookies: string = header['set-cookie'].join() + return { cookies: newCookies } +} + +const extractCSRF = (cookies: string) => + /_csrf=(.*); Max-Age=86400000; Path=\/; HttpOnly,XSRF-TOKEN=(.*); Path=\//.exec( + cookies + )![2] diff --git a/api/src/routes/web/web.ts b/api/src/routes/web/web.ts index 7cb74e1..c64478f 100644 --- a/api/src/routes/web/web.ts +++ b/api/src/routes/web/web.ts @@ -1,23 +1,25 @@ import express from 'express' import { WebController } from '../../controllers/web' -import { loginWebValidation } from '../../utils' +import { authenticateAccessToken } from '../../middlewares' +import { authorizeValidation, loginWebValidation } from '../../utils' const webRouter = express.Router() const controller = new WebController() webRouter.get('/', async (req, res) => { + let response try { - const response = await controller.home() - + response = await controller.home() + } catch (_) { + response = 'Web Build is not present' + } finally { res.cookie('XSRF-TOKEN', req.csrfToken()) return res.send(response) - } catch (_) { - return res.send('Web Build is not present') } }) -webRouter.post('/login', async (req, res) => { +webRouter.post('/SASLogon/login', async (req, res) => { const { error, value: body } = loginWebValidation(req.body) if (error) return res.status(400).send(error.details[0].message) @@ -25,16 +27,32 @@ webRouter.post('/login', async (req, res) => { const response = await controller.login(req, body) res.send(response) } catch (err: any) { - res.status(400).send(err.toString()) + res.status(403).send(err.toString()) } }) +webRouter.post( + '/SASLogon/authorize', + authenticateAccessToken, + async (req, res) => { + const { error, value: body } = authorizeValidation(req.body) + if (error) return res.status(400).send(error.details[0].message) + + try { + const response = await controller.authorize(req, body) + res.send(response) + } catch (err: any) { + res.status(403).send(err.toString()) + } + } +) + webRouter.get('/logout', async (req, res) => { try { await controller.logout(req) res.status(200).send('OK!') } catch (err: any) { - res.status(400).send(err.toString()) + res.status(403).send(err.toString()) } }) diff --git a/api/src/utils/validation.ts b/api/src/utils/validation.ts index a18cef9..ba9898f 100644 --- a/api/src/utils/validation.ts +++ b/api/src/utils/validation.ts @@ -13,8 +13,6 @@ export const loginWebValidation = (data: any): Joi.ValidationResult => export const authorizeValidation = (data: any): Joi.ValidationResult => Joi.object({ - username: usernameSchema.required(), - password: passwordSchema.required(), clientId: Joi.string().required() }).validate(data) diff --git a/web/src/App.tsx b/web/src/App.tsx index 1f13e31..92cb547 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -10,6 +10,7 @@ import Drive from './containers/Drive' import Studio from './containers/Studio' import { AppContext } from './context/appContext' +import AuthCode from './containers/AuthCode' function App() { const appContext = useContext(AppContext) @@ -20,9 +21,6 @@ function App() {
- - - @@ -47,7 +45,7 @@ function App() { - + diff --git a/web/src/components/login.tsx b/web/src/components/login.tsx index cca13e2..11451c9 100644 --- a/web/src/components/login.tsx +++ b/web/src/components/login.tsx @@ -1,56 +1,27 @@ import axios from 'axios' import React, { useState, useContext } from 'react' -import { useLocation } from 'react-router-dom' import PropTypes from 'prop-types' -import { CssBaseline, Box, TextField, Button, Typography } from '@mui/material' +import { CssBaseline, Box, TextField, Button } from '@mui/material' import { AppContext } from '../context/appContext' -const getAuthCode = async (credentials: any) => - axios.post('/SASjsApi/auth/authorize', credentials).then((res) => res.data) - const login = async (payload: { username: string; password: string }) => - axios.post('/login', payload).then((res) => res.data) + axios.post('/SASLogon/login', payload).then((res) => res.data) -const Login = ({ getCodeOnly }: any) => { - const location = useLocation() +const Login = () => { const appContext = useContext(AppContext) const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [errorMessage, setErrorMessage] = useState('') - let error: boolean - const [displayCode, setDisplayCode] = useState(null) const handleSubmit = async (e: any) => { - error = false setErrorMessage('') e.preventDefault() - if (getCodeOnly) { - const params = new URLSearchParams(location.search) - const responseType = params.get('response_type') - if (responseType === 'code') { - const clientId = params.get('client_id') - - const { code } = await getAuthCode({ - clientId, - username, - password - }).catch((err: any) => { - error = true - setErrorMessage(err.response.data) - return {} - }) - if (!error) return setDisplayCode(code) - return - } - } - const { loggedIn, user } = await login({ username, password }).catch((err: any) => { - error = true setErrorMessage(err.response.data) return {} }) @@ -62,21 +33,6 @@ const Login = ({ getCodeOnly }: any) => { } } - if (displayCode) { - return ( - - -
-

Authorization Code

- - {displayCode} - - -
-
- ) - } - return ( {

Welcome to SASjs Server!

- {getCodeOnly && ( -

- Provide credentials to get authorization code. -

- )} -
- + axios.post('/SASLogon/authorize', credentials).then((res) => res.data) + +const AuthCode = () => { + const location = useLocation() + const [displayCode, setDisplayCode] = useState(null) + const [errorMessage, setErrorMessage] = useState('') + + useEffect(() => { + requestAuthCode() + }, []) + + const requestAuthCode = async () => { + setErrorMessage('') + + const params = new URLSearchParams(location.search) + + const responseType = params.get('response_type') + if (responseType !== 'code') + return setErrorMessage('response type is not support') + + const clientId = params.get('client_id') + if (!clientId) return setErrorMessage('clientId is not provided') + + setErrorMessage('Fetching auth code... ') + const { code } = await getAuthCode({ + clientId + }) + .then((res) => { + setErrorMessage('') + return res + }) + .catch((err: any) => { + setErrorMessage(err.response.data) + return { code: null } + }) + return setDisplayCode(code) + } + + return ( + + +
+

Authorization Code

+ {displayCode && ( + + {displayCode} + + )} + {errorMessage && {errorMessage}} + +
+
+ ) +} + +export default AuthCode