WIP token

This commit is contained in:
newtbot 2024-01-16 04:43:39 +08:00
parent 290d0653d9
commit 7e4b2d8026
19 changed files with 534 additions and 77 deletions

12
api.MD
View File

@ -1,6 +1,8 @@
//location //location
//get all //get all
curl localhost/api/v0/location curl localhost/api/v0/location
curl localhost/api/v0/location -H "Authorization: ${1a3eabe1-e1b2-46df-b846-585540c68368}"
//get id //get id
curl http://localhost/api/v0/location/3 curl http://localhost/api/v0/location/3
@ -159,3 +161,13 @@ http://localhost/api/v0/sensor-data/filter?windspeed=highest&limit=1
//pagination //pagination
http://localhost/api/v0/sensor-data/data?week=1&sensorid=1&locationid=1&page=2&pagesize=10 http://localhost/api/v0/sensor-data/data?week=1&sensorid=1&locationid=1&page=2&pagesize=10
curl localhost/api/v0/user/new -H "Content-Type: application/json" -X POST -d '{"username": "testuser", "password": "thisisthesystemuserpasswordnoob", "email": "testuser@ecosaver.com", "address": "Nanyang Polytechnic 180 Ang Mo Kio Avenue 8 Singapore 569830", "phone": "12345678"}'
curl localhost/api/v0/apikey/new -H "Content-Type: application/json" -X POST -d '{"userid": 1, "permission": "canWrite"'

View File

@ -5,8 +5,8 @@ const { userModel } = require("./userModel");
sequelize.sync(); sequelize.sync();
const apikeyModel = sequelize.define( const apikeyModel = sequelize.define(
"apikey", "apikey",
{ {
id: { id: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: true, allowNull: true,
@ -16,39 +16,39 @@ const apikeyModel = sequelize.define(
isNumeric: true, isNumeric: true,
}, },
}, },
userid:{ userid: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: false, allowNull: false,
validate: { validate: {
isNumeric: true, isNumeric: true,
}, },
//fk //fk
references: { references: {
model: userModel, model: userModel,
key: "id", key: "id",
}, },
}, },
apikey: { apikey: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false, allowNull: false,
length: 255, length: 255,
unique: true, unique: true,
validate: { validate: {
notEmpty: true, notEmpty: true,
len: [1, 255], len: [1, 255],
}, },
}, },
permission: { permission: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: false, allowNull: false,
length: 255, length: 255,
validate: { validate: {
notEmpty: true, notEmpty: true,
len: [1, 255], len: [1, 255],
isIn: [['canRead' , 'canWrite']], isIn: [["canRead", "canWrite"]],
}, },
}, },
createdAt: { createdAt: {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: true, allowNull: true,
}, },
@ -56,11 +56,10 @@ const apikeyModel = sequelize.define(
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: true, allowNull: true,
}, },
}, },
{ {
timestamps: true, timestamps: true,
} }
);
)
module.exports = { apikeyModel }; module.exports = { apikeyModel };

View File

@ -0,0 +1,30 @@
const { sequelize } = require("../database/mySql.js");
const { apikeyModel } = require("../database/model/apikeyModel.js");
const { userModel } = require("../database/model/userModel.js");
const { Op, Sequelize } = require("sequelize");
async function getUser() {
const user = await userModel.findAll();
return user;
}
async function addUser(user) {
//console.log(user);
await userModel.create(user);
}
async function getAPIKey() {
const apikey = await apikeyModel.findAll();
return apikey;
}
async function addAPIKey(apikey) {
await apikeyModel.create(apikey);
}
module.exports = {
getUser,
addUser,
getAPIKey,
addAPIKey,
};

View File

@ -0,0 +1,37 @@
const bcrypt = require('bcrypt');
const saltRounds = 10;
//https://github.com/kelektiv/node.bcrypt.js#readme
/*
// Load hash from your password DB.
bcrypt.compare(myPlaintextPassword, hash, function(err, result) {
// result == true
});
bcrypt.compare(someOtherPlaintextPassword, hash, function(err, result) {
// result == false
});
*/
/*
//hash with salt
bcrypt.hash(myPlaintextPassword, saltRounds, function(err, hash) {
// Store hash in your password DB.
});
*/
async function hashPassword(password) {
return await bcrypt.hash(password, saltRounds);
}
async function hashAPIKey(apikey) {
return await bcrypt.hash(apikey, saltRounds);
}
module.exports = {
hashPassword,
hashAPIKey,
};

View File

@ -0,0 +1,17 @@
/*
const crypto = require('crypto');
Calling the UUID method returns a UUID of standard length that you can use in your program.
let uuid = crypto.randomUUID();
console.log(uuid);
*/
const crypto = require('crypto');
async function generateUUID() {
let uuid = crypto.randomUUID();
return uuid;
}
module.exports = { generateUUID };

View File

@ -12,16 +12,11 @@ app.disable("x-powered-by");
app.use(express.json()); app.use(express.json());
app.set("json spaces", 2); app.set("json spaces", 2);
//middleware logic ( called by next() ) //middleware logic ( called by next() )
//app.use('/api/v0', APIlogger, require('../routes/api_route.js'));
//app.use('/api/v0', require('../middleware/ApiKey.js'));
a//pp.use('/api/v0', APIlogger, require('../routes/api_route.js'));
//route logic //route logic
//app.use("/api/v0", require("../routes/api_route.js")); app.use("/api/v0", require("../routes/api_routes")); //consumerWebsite\routes\api_routes.js
// Catch 404 and forward to error handler. If none of the above routes are // Catch 404 and forward to error handler. If none of the above routes are
// used, this is what will be called. // used, this is what will be called.

View File

@ -0,0 +1,10 @@
'use strict';
const router = require('express').Router();
router.use('/user', require('./user'));
router.use('/apikey', require('./apikey'));
module.exports = router;

View File

@ -0,0 +1,69 @@
const { getAPIKey , addAPIKey } = require("../functions/apiDatabase.js");
const { hashAPIKey } = require("../functions/bcrypt.js");
const { generateUUID } = require("../functions/generateUUID.js");
const express = require("express");
const router = express.Router();
router.get("/", async (req, res, next) => {
try {
const location = await getAPIKey();
res.status(200).json(location);
} catch (error) {
console.error(error);
next(error);
}
});
/*
1) ensure user is logged in (frontend session validation blah or wtv)
2) when user click on generate api key button, it will generate a random api key
3) hash the api key
4) store the api key in database
*/
router.post("/new", async (req, res, next) => {
try {
let uuid = await generateUUID()
//attach uuid to req.body
req.body.apikey = uuid
//hash apikey
req.body.apikey = await hashAPIKey(req.body.apikey)
await addAPIKey(req.body);
res.sendStatus(200);
} catch (error) {
console.error(error);
next(error);
}
});
//update
//delete
//getbyid
module.exports = router;
/*
async function addAPIKey(userId) {
let apikey = await generateUUID()
apikey = await hashAPIKey(req.body.apikey)
let token = await apikeyModel.create({apikey, userId});
return `${token.id}-${apikey}`
}
router.post("/new", async (req, res, next) => {
try {
let apikey = await addAPIKey(req.body.userid)
res.json({apiKey: apikey})
} catch (error) {
console.error(error);
next(error);
}
});
*/

View File

@ -0,0 +1,42 @@
const { getUser, addUser } = require("../functions/apiDatabase.js");
const { hashPassword } = require("../functions/bcrypt.js");
const express = require("express");
const router = express.Router();
router.get("/", async (req, res, next) => {
try {
const location = await getUser();
res.status(200).json(location);
} catch (error) {
console.error(error);
next(error);
}
});
/*
1) req.body is taken from html form or wtv
2) bcrpyt and hash the password on the server side
3) pass to db
*/
router.post("/new", async (req, res, next) => {
try {
//pass pass to hashPassword
let hash = await hashPassword(req.body.password);
//add hash back to req.body
req.body.password = hash;
await addUser(req.body);
res.sendStatus(200);
} catch (error) {
console.error(error);
next(error);
}
});
//login
//update
//delete
//getbyid
module.exports = router;

View File

@ -0,0 +1,65 @@
"use strict";
const { Sequelize, DataTypes } = require("sequelize");
const { sequelize } = require("../mySQL");
const { userModel } = require("./userModel");
//sequelize.sync();
const apikeyModel = sequelize.define(
"apikey",
{
id: {
type: DataTypes.INTEGER,
allowNull: true,
primaryKey: true,
autoIncrement: true,
validate: {
isNumeric: true,
},
},
userid: {
type: DataTypes.INTEGER,
allowNull: false,
validate: {
isNumeric: true,
},
//fk
references: {
model: userModel,
key: "id",
},
},
apikey: {
type: DataTypes.STRING,
allowNull: false,
length: 255,
unique: true,
validate: {
notEmpty: true,
len: [1, 255],
},
},
permission: {
type: DataTypes.STRING,
allowNull: false,
length: 255,
validate: {
notEmpty: true,
len: [1, 255],
isIn: [["canRead", "canWrite"]],
},
},
createdAt: {
type: DataTypes.DATE,
allowNull: true,
},
updatedAt: {
type: DataTypes.DATE,
allowNull: true,
},
},
{
timestamps: true,
}
);
module.exports = { apikeyModel };

View File

@ -5,7 +5,7 @@ const { locationModel } = require("./locationModel");
const { sensorModel } = require("./sensorModel"); const { sensorModel } = require("./sensorModel");
const { isJson } = require('../../functions/validateData'); const { isJson } = require('../../functions/validateData');
sequelize.sync(); //sequelize.sync();
const sensorDataModel = sequelize.define( const sensorDataModel = sequelize.define(
"sensorData", "sensorData",
{ {

View File

@ -0,0 +1,94 @@
"use strict";
const { Sequelize, DataTypes } = require("sequelize");
const { sequelize } = require("../mySQL");
const {
isAlphaNumericWithSpacesAndDash,
isAddress,
} = require("../../functions/validateData");
//sequelize.sync();
const userModel = sequelize.define(
"user",
{
id: {
type: DataTypes.INTEGER,
allowNull: true,
primaryKey: true,
autoIncrement: true,
validate: {
isNumeric: true,
},
},
username: {
type: DataTypes.STRING,
allowNull: false,
length: 60,
validate: {
notEmpty: true,
len: [1, 60],
isAlphaNumericWithSpacesAndDash(value) {
if (!isAlphaNumericWithSpacesAndDash(value)) {
throw new Error("Invalid characters in username");
}
},
},
},
password: {
type: DataTypes.STRING,
allowNull: false,
length: 255,
validate: {
notEmpty: true,
len: [1, 255],
},
},
email: {
type: DataTypes.STRING,
allowNull: false,
length: 60,
unique: true,
validate: {
notEmpty: true,
len: [1, 60],
isEmail: true,
},
},
address: {
type: DataTypes.STRING,
allowNull: true,
length: 255,
validate: {
notEmpty: true,
len: [1, 255],
isAddress(value) {
if (!isAddress(value)) {
throw new Error("Invalid address");
}
},
},
},
phone: {
type: DataTypes.STRING,
allowNull: true,
length: 20,
validate: {
notEmpty: true,
len: [1, 20],
isNumeric: true,
},
},
//utc time
createdAt: {
type: DataTypes.DATE,
allowNull: true,
},
updatedAt: {
type: DataTypes.DATE,
allowNull: true,
},
},
{
timestamps: true,
}
);
module.exports = { userModel };

View File

@ -1,6 +1,7 @@
const { sequelize } = require("../Database/mySql.js"); const { sequelize } = require("../Database/mySql.js");
const { api_log_Model } = require("../Database/model/apiLogModel.js"); const { api_log_Model } = require("../Database/model/apiLogModel.js");
const { sensorDataModel } = require("../Database/model/sensorDataModel.js"); const { sensorDataModel } = require("../Database/model/sensorDataModel.js");
const { apikeyModel } = require("../Database/model/apiKeyModel.js");
async function insertLogData(log){ async function insertLogData(log){
try{ try{
@ -32,10 +33,19 @@ async function insertDatatoDB(data) {
catch (error) { catch (error) {
console.error(error); console.error(error);
} }
}
async function checkAPikey(unverified){
const apikey = apikeyModel.findOne({
where: {
apikey: unverified
}
});
return apikey;
} }
module.exports = { insertLogData , insertDatatoDB}; module.exports = { insertLogData , insertDatatoDB , checkAPikey};

View File

@ -0,0 +1,38 @@
const bcrypt = require('bcrypt');
const saltRounds = 10;
//https://github.com/kelektiv/node.bcrypt.js#readme
/*
// Load hash from your password DB.
bcrypt.compare(myPlaintextPassword, hash, function(err, result) {
// result == true
});
bcrypt.compare(someOtherPlaintextPassword, hash, function(err, result) {
// result == false
});
*/
/*
//hash with salt
bcrypt.hash(myPlaintextPassword, saltRounds, function(err, hash) {
// Store hash in your password DB.
});
*/
async function hashAPIKey(apikey) {
return await bcrypt.hash(apikey, saltRounds);
}
async function compareAPIKey(apikey, hash) {
return await bcrypt.compare(apikey, hash);
}
module.exports = {
hashAPIKey,
compareAPIKey
};

View File

@ -1,18 +1,62 @@
const { compareAPIKey } = require('../functions/bcrypt.js');
const { checkAPikey } = require('../functions/database.js');
async function apikeyCheck(req, res, next) {
//const authHeader = req.headers.authorization
try{
let apikey = req.headers.authorization
if(!apikey){
throw new Error('NotAuthed')
}
else{
//compare apikey to db
function apiKeyMiddleware(req, res, next) { }
const apiKey = req.headers['x-api-key']; next()
}catch(error){
if (!apiKey) { next(error);
return res.status(401).json({ error: 'API key is missing' });
} }
//logic to check db?
if (apiKey !== 'YOUR_API_KEY') {
return res.status(403).json({ error: 'Invalid API key' });
}
// API key is valid, continue to the next middleware or route handler
next();
} }
module.exports = { apiKeyMiddleware } module.exports = { apikeyCheck };
/*
1) take user supplied api key
2) hash and salt
3) compare to stored hash and salt in db
4) if match, check permissions
5) if permissions allow, continue
6) else throw error
*/
/*
I plan to seed some data in user and api
Than use the system info and my API middleware will somehow check the supplied API key and check
If it's correct API key and has canWrite perms
I allow it to access put and post
async function auth(req, res, next){
try{
let token = // get token
req.token = token
if(req.method === 'GET' && token.canRead){
return next()
}
if(req.method === 'POST' && token.canWrite){
return next()
}
throw new Error('NotAuthed')
}catch(error){
next(error);
}
}
*/

View File

@ -2,6 +2,7 @@ const express = require("express");
const helmet = require("helmet"); const helmet = require("helmet");
const { rateLimit } = require("express-rate-limit"); const { rateLimit } = require("express-rate-limit");
const { APIlogger } = require('../middleware/apiLogger.js'); const { APIlogger } = require('../middleware/apiLogger.js');
const { apikeyCheck } = require('../middleware/apiKey.js');
const app = express(); const app = express();
app.use(helmet()); app.use(helmet());
@ -30,14 +31,10 @@ app.set("json spaces", 2);
/* /*
middleware logic ( called by next() ) middleware logic ( called by next() )
*/ */
//app.use('/api/v0', require('../middleware/ApiKey.js')); app.use("/api/seed/v0", [ apikeyCheck , APIlogger] ,require("../routes/seed_route.js"));
app.use('/api/v0', APIlogger, require('../routes/api_route.js')); app.use('/api/v0', [apikeyCheck, APIlogger] ,require("../routes/api_route.js")); //webserver\routes\api_route.js
//route logic
app.use("/api/v0", require("../routes/api_route.js"));
//seed logic
app.use("/api/seed/v0", require("../routes/seed_route.js"));
// Catch 404 and forward to error handler. If none of the above routes are // Catch 404 and forward to error handler. If none of the above routes are
// used, this is what will be called. // used, this is what will be called.

View File

@ -20,7 +20,7 @@ router.get("/", async (req, res, next) => {
next(error); next(error);
} }
}); });
/*
//add location //add location
router.post("/new", async (req, res, next) => { router.post("/new", async (req, res, next) => {
@ -58,7 +58,7 @@ router.delete("/delete", async (req, res, next) => {
} }
}); });
*/
//get location by id //get location by id
router.get("/:id", async (req, res, next) => { router.get("/:id", async (req, res, next) => {

View File

@ -18,7 +18,7 @@ router.get("/", async (req, res, next) => {
next(error); next(error);
} }
}); });
/*
router.post("/new", async (req, res, next) => { router.post("/new", async (req, res, next) => {
try { try {
@ -52,7 +52,6 @@ router.delete("/delete", async (req, res, next) => {
next(error); next(error);
} }
}); });
*/
router.get("/:id", async (req, res, next) => { router.get("/:id", async (req, res, next) => {
try { try {
const sensor = await getSensorById(req.params.id); const sensor = await getSensorById(req.params.id);

View File

@ -21,7 +21,7 @@ router.get("/", async (req, res, next) => {
next(error); next(error);
} }
}); });
/*
router.post("/new", async (req, res, next) => { router.post("/new", async (req, res, next) => {
try { try {
const { id, id_sensor, id_location, sensordata } = req.body; const { id, id_sensor, id_location, sensordata } = req.body;
@ -54,7 +54,6 @@ router.delete("/delete", async (req, res, next) => {
next(error); next(error);
} }
}); });
*/
router.get("/data", async (req, res, next) => { router.get("/data", async (req, res, next) => {
try { try {
console.log(req.query); console.log(req.query);