Skip to content

Commit 276c487

Browse files
author
Shreyas Nayak
committed
JWT Token authentication is added
1 parent 5ffb3f2 commit 276c487

8 files changed

Lines changed: 229 additions & 16 deletions

File tree

.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
JWT_KEY=QX2A0p84VmmLF3NYz3uHPx1hLuhT2U2K

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,12 @@ function main(event, context, callback) {
7575
}
7676

7777
main(G_EVENT_NAME,G_CONTEXT,G_CALLBACK);
78-
```
78+
```
79+
80+
# Generate token
81+
Genarte the .env file
82+
`JWT_KEY=MY_SECRET_KEY`
83+
84+
`apt-get update`
85+
86+
`apt-get install jq jw`

index.mjs

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ import multer from 'multer';
99
import axios from 'axios';
1010
import swaggerUi from 'swagger-ui-express';
1111
import { v4 as uuidv4 } from 'uuid';
12+
import dotenv from 'dotenv';
13+
import jwt from 'jsonwebtoken';
14+
15+
dotenv.config();
16+
1217

1318
/* Utility to get __dirname in ES modules */
1419
const __filename = fileURLToPath(import.meta.url);
@@ -36,6 +41,33 @@ const openApiFilePath = path.join(__dirname, 'openapi.json');
3641
const openApiDocument = JSON.parse(fs.readFileSync(openApiFilePath, 'utf8'));
3742
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(openApiDocument));
3843

44+
45+
/* Security Middleware */
46+
const authenticateToken = (req, res, next) => {
47+
const authHeader = req.headers['authorization'];
48+
const token = authHeader && authHeader.split(' ')[1];
49+
if (!token) return res.status(401).json({
50+
"status": "ERROR",
51+
"code": 401,
52+
"message": "Unauthorized access",
53+
"payload": {}
54+
});
55+
56+
jwt.verify(token, process.env.JWT_KEY, (err, decoded) => {
57+
if (err || (decoded.expire_at == undefined || decoded.issued_at == undefined || decoded.issuer == undefined || decoded.namespace == undefined)) {
58+
return res.status(401).json({
59+
"status": "ERROR",
60+
"code": 401,
61+
"message": "Unauthorized access",
62+
"payload": {}
63+
});
64+
}
65+
req.decoded = decoded;
66+
next();
67+
});
68+
};
69+
70+
3971
app.get('/server/health', (req, res) => {
4072
res.status(200).send('OK');
4173
});
@@ -44,29 +76,32 @@ app.head('/server/health', (req, res) => {
4476
res.status(200).send();
4577
});
4678

47-
app.get('/function/list', (req, res) => {
48-
fs.readdir('functions', (err, files) => {
79+
app.get('/function/list', authenticateToken, (req, res) => {
80+
fs.readdir(`functions/${req.decoded.namespace}`, (err, files) => {
4981
if (err) {
5082
return res.status(500).json({
5183
"status": "ERROR",
5284
"code": 500,
53-
"message": err.message,
85+
"message": "Namespace does not exist, create function before listing",
5486
"payload": {}
5587
});
5688
}
5789

90+
// Remove extensions from filenames
91+
const fileNamesWithoutExtension = files.map(file => path.basename(file, path.extname(file)));
92+
5893
res.status(200).json({
5994
"status": "OK",
6095
"code": 200,
6196
"message": "",
6297
"payload": {
63-
"functions": files
98+
"function_ids": fileNamesWithoutExtension
6499
}
65100
});
66101
});
67102
});
68103

69-
app.post('/function/create', upload.single('file'), (req, res) => {
104+
app.post('/function/create', authenticateToken, upload.single('file'), (req, res) => {
70105
if (!req.file) {
71106
return res.status(400).json({
72107
"status": "ERROR",
@@ -79,7 +114,16 @@ app.post('/function/create', upload.single('file'), (req, res) => {
79114
const file = req.file;
80115
const file_id = generateAlphanumericName(25);
81116
const file_name = `${file_id}.mjs`;
82-
fs.renameSync(file.path, `functions/${file_name}`);
117+
const directory = `functions/${req.decoded.namespace}`;
118+
119+
// Ensure the directory exists
120+
if (!fs.existsSync(directory)) {
121+
fs.mkdirSync(directory, { recursive: true });
122+
}
123+
124+
// Rename the uploaded file and move it to the correct directory
125+
fs.renameSync(file.path, path.join(directory, file_name));
126+
83127
res.status(200).json({
84128
"status": "OK",
85129
"code": 200,
@@ -90,16 +134,16 @@ app.post('/function/create', upload.single('file'), (req, res) => {
90134
});
91135
});
92136

93-
app.delete('/function/:function_id', (req, res) => {
137+
app.delete('/function/:function_id', authenticateToken, (req, res) => {
94138
const function_id = req.params.function_id;
95139
const file_name = `${function_id}.mjs`;
96140

97-
fs.unlink(`functions/${file_name}`, (err) => {
141+
fs.unlink(`functions/${req.decoded.namespace}/${file_name}`, (err) => {
98142
if (err) {
99143
return res.status(500).json({
100144
"status": "ERROR",
101145
"code": 500,
102-
"message": err.message,
146+
"message": "Failed to delete function",
103147
"payload": {}
104148
});
105149
}
@@ -113,16 +157,16 @@ app.delete('/function/:function_id', (req, res) => {
113157
});
114158
});
115159

116-
app.post('/function/execute/:function_id', async (req, res) => {
160+
app.post('/function/execute/:function_id', authenticateToken, async (req, res) => {
117161
await new Promise((resolve, reject) => {
118162
if (!req.body || Object.keys(req.body).length === 0) {
119163
reject(new Error("Request body is empty or not valid JSON"));
120164
return;
121165
}
122166
const function_id = req.params.function_id;
123-
fs.readFile(`functions/${function_id}.mjs`, 'utf8', (err, code) => {
167+
fs.readFile(`functions/${req.decoded.namespace}/${function_id}.mjs`, 'utf8', (err, code) => {
124168
if (err) {
125-
reject(new Error(err.message));
169+
reject(new Error("Function does not exist"));
126170
return;
127171
}
128172
const { event_name, event_data } = req.body;

jwt_token_stuct.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"expire_at": "1735713720",
3+
"issued_at": "1721994397",
4+
"issuer": "shreyas",
5+
"namespace": "neo"
6+
}

openapi.json

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,23 @@
22
"openapi": "3.0.0",
33
"info": {
44
"title": "NodeJS-FAAS",
5-
"description": "This project aims to achieve something similar to Function as a Service (FaaS) without the overhead of Docker or Kubernetes, with extremely low infrastructure requirements.",
5+
"description": "This project aims to achieve something similar to Function as a Service (FaaS) without the overhead of Docker or Kubernetes, with extremely low infrastructure requirements. \n Github repository link : [Click Here](https://github.com/shreyasnayak/nodejs-faas)",
66
"version": "1.0.0"
77
},
8+
"components": {
9+
"securitySchemes": {
10+
"BearerAuth": {
11+
"type": "http",
12+
"scheme": "bearer",
13+
"bearerFormat": "JWT"
14+
}
15+
}
16+
},
17+
"security": [
18+
{
19+
"BearerAuth": []
20+
}
21+
],
822
"paths": {
923
"/server/health": {
1024
"get": {
@@ -73,7 +87,7 @@
7387
"code": 200,
7488
"message": "",
7589
"payload": {
76-
"functions": ["VmgtJbe8I2.mjs", "nmgtkbe8I2.mjs"]
90+
"function_ids": ["VmgtJbe8I2", "nmgtkbe8I2"]
7791
}
7892
}
7993
}

package-lock.json

Lines changed: 118 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
"axios": "^1.6.8",
1313
"body-parser": "^1.20.2",
1414
"cors": "^2.8.5",
15+
"dotenv": "^16.4.5",
1516
"express": "^4.19.2",
17+
"jsonwebtoken": "^9.0.2",
1618
"multer": "^1.4.5-lts.1",
1719
"swagger-ui-express": "^5.0.1",
1820
"uuid": "^9.0.1"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const axios = require('axios');
2+
3+
function main(event, context, callback) {
4+
if(event=="get_nasa_image") {
5+
const { key } = context;
6+
axios.get(`https://api.nasa.gov/planetary/apod?api_key=${key}`)
7+
.then(response => {
8+
if(callback) callback({
9+
explanation: response.data.explanation
10+
});
11+
})
12+
.catch(error => {
13+
if(callback) callback({
14+
error: error.message
15+
});
16+
});
17+
}
18+
}
19+
20+
21+
main(G_EVENT_NAME,G_CONTEXT,G_CALLBACK);

0 commit comments

Comments
 (0)