Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CHANGE] #84, writing key check to be simpler, introduced scripts for… #135

Merged
merged 7 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@
//"@typescript-eslint/no-unused-vars": "warn"
"jest/no-conditional-expect": "off"
},
"ignorePatterns": ["dist", "jest.config.js", "httpdocs", "webpack.config.js", "src/client"]
"ignorePatterns": ["dist", "jest.config.js", "httpdocs", "webpack.config.js", "src/client", "init"]

}
7 changes: 3 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ jobs:
NODE_ENV: ${{ vars.NODE_ENV }}
LOCALHOST: ${{ vars.LOCALHOST }}
LOCALHOSTV6: ${{ vars.LOCALHOSTV6 }}
KEYA: ${{ secrets.KEYA }}
KEYB: ${{ secrets.KEYB }}
KEY: ${{ secrets.KEY }}
USER_TEST: ${{ secrets.USER_TEST }}

steps:
Expand All @@ -30,8 +29,8 @@ jobs:
- run: npm run build --if-present
- name: Start server
run: |
sudo NODE_ENV=$NODE_ENV LOCALHOST=$LOCALHOST LOCALHOSTV6=$LOCALHOSTV6 KEYA=$KEYA KEYB=$KEYB USER_TEST=$USER_TEST npm start &
sleep 15 # Give server some time to start
sudo NODE_ENV=$NODE_ENV LOCALHOST=$LOCALHOST LOCALHOSTV6=$LOCALHOSTV6 KEY=$KEY USER_TEST=$USER_TEST npm start &
sleep 16 # Give server some time to start
- name: Check if server is running
run: |
curl --fail http://localhost:80 || exit 1
Expand Down
91 changes: 84 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ Leaflet Osmand React ExpressJS Coordinates (X)
> Remember an "X" marks the spot.

## Goal
**Technical:** Recieve and store coordinates via webhook and display them on a map through an interactive frontend.
**Personal:** The intend of this project is to get familiar with the listed technologies.

## Progress
View [milestones](https://github.com/Type-Style/LOREX/milestones) and the [board](https://github.com/users/Type-Style/projects/1) to witness progression
Recieve and store coordinates via webhook and display them on a map through an interactive frontend.

## Installation
### Prerequisites
Expand All @@ -20,6 +16,87 @@ Run the install command for example via npm
`npm install`

After completion without errors hit
`npx ts-node src/app.ts`
`npm run build`

Once complete you can start the server using
`npm run start`

> [!TIP]
Build and start can be combined using
`npm run dev`

### Generating Key and Password
`Error Message: KEY is missing in environment variables`
Before you are able to login and use the webapplication, environment variables need to be setup.
Therefore you need to create a file `.env` and place it in the root of the project.
This file will be filled with secrets to protect your instance of LOREX.

####1st. Generate Key
Usage: open console run: node init/generateKey.js
type desired key and hit enter
copy output to .env add a line starting with:
KEY=
directly followed by your output

####2nd Generate Password(s)
Prerequisite: KEY already generated!
_(may require server restart)_
Run the build command from the package.json (npm run build)
Then call the compiled version of this script using the key as environment variable like so:
KEY=your-key node ./init/generatePassword.js
Enter your password
Copy that to the Environment Variables and .env file
USER_WHATEVER=
followed by the output of the console

> [!IMPORTANT]
In order to run automatic tests and create example data is is highly recommended to have a USER_TEST with the password of `test`
THe test user cannot be used in an production environment

Once completed rebuild / restart the server and open up localhost/login
Login is now possible using the Username from the .env file in this example "WHATEVER" and the password that was created in the previous step.

**Now the application is ready**


## Generating Data
### Example Data
Build and start the server for example using
`npm run dev`
Wait for the server to start and webpack to compile the assets.
Once done use a second command line to run
`npm run test:data`

This generates 6 entries each 30s appart and calls the webserver to store this data.
The writing of data can be seen in the first console where the server is running.
Also once logged in under localhost, a map is visible showing a route westbound from the brandenburg gate.

### Calling writing route manually
Data can be generated by calling the /write rout of the server.
Here is an example:

`http://localhost/write?user=xx&lat=53.5000&lon=10.0&timestamp=1720691648188&hdop=10.0&altitude=1000.000&speed=100.000&heading=180.0&key=test`


In order to pass validation use the correct key _(or `test` in development envrioment)_ and create a valid [UNIX timestamp]([url](https://currentmillis.com/)).
For example by using this javascript code:
`var a = new Date().getTime();
copy(a);
a;`

## Using on Production
### A note on security
This application is not developed with https built in support.
> [!WARNING] It is advised to run this application in `production` mode behind a proxy that uses https for security reasons

### Getting data
Similar to the section Generating Data and Calling writing route manually, the application relies on data being provided using a webhook.
Well how data is collected and what data is pushed to the system is user preference.
Feel free to build you own application to do so.

This application is designed to be used with the [OSMAND+ mobile app]([url](https://osmand.net/)).
Due to a plugin called [Triprecording]([url](https://osmand.net/docs/user/plugins/trip-recording/))
Using the above link or by [clicking here](https://osmand.net/docs/user/plugins/trip-recording#recording-settings) more information can be found to setup webtracking or "online tracking"


**Now the server is ready**
30 changes: 30 additions & 0 deletions init/generateKey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Usage: open console run: node init/generateKey.js
* type desired key and hit enter
* copy output to .env add a line starting with:
* KEY=
* directly followed by your output
*/

// Import required modules
const readline = require('readline');

// set up readline to read input from the console
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});

// Prompt user for input
rl.question('Enter the string to be encoded: ', (input) => {
// encode to escape special chars
const escapedString = encodeURIComponent(input);

// convert the escaped string to base64
const base64String = Buffer.from(escapedString).toString('base64');

// print the result
console.log('Base64 Encoded String:', base64String);

rl.close();
});
29 changes: 29 additions & 0 deletions init/generatePassword.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* This is used to setup Passwords initially
* You can create passwords using the same logic as in the environment
* Prerequisite: You need to have KEY already generated!
* Run the build command from the package.json (npm run build)
* Then call the compiled version of this script using the key as environment variable like so:
* KEY=your-key node ./init/generatePassword.js
* Enter your password
* Copy that to the Environment Variables and .env file
* USER_WHATEVER=
* followed by the output of the console
*/

// Import required modules
const readline = require('readline');
const { crypt } = require('../dist/scripts/crypt');

// Set up readline to read input from the console
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});

// Prompt user for input
rl.question('Enter Password to be generated: ', async (input) => {
const cryptedPassword = await crypt(input);
console.log(cryptedPassword);
rl.close();
});
28 changes: 14 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@
"@changey/react-leaflet-markercluster": "^4.0.0-rc1",
"@emotion/react": "^11.13.0",
"@emotion/styled": "^11.11.5",
"@mui/icons-material": "^5.16.5",
"@mui/material": "^5.16.6",
"@mui/icons-material": "^5.16.7",
"@mui/material": "^5.16.7",
"@types/leaflet-rotatedmarker": "^0.2.5",
"axios": "^1.7.4",
"bcrypt": "^5.1.1",
Expand All @@ -79,7 +79,7 @@
"express": "^4.19.2",
"express-rate-limit": "^7.4.0",
"express-slow-down": "^2.0.3",
"express-validator": "^7.1.0",
"express-validator": "^7.2.0",
"helmet": "^7.1.0",
"hpp": "^0.2.3",
"jsonwebtoken": "^9.0.2",
Expand Down
5 changes: 2 additions & 3 deletions src/models/entry.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { NextFunction, Request, Response } from 'express';
import { checkExact, query } from 'express-validator';
import { compare } from '@src/scripts/crypt';
import { create as createError } from '@src/middleware/error';
import * as file from '@src/scripts/file';
import { getTime } from '@src/scripts/time';
Expand Down Expand Up @@ -138,12 +137,12 @@ export function checkTime(value: string) {

async function checkKey(value: string) {
if (!value) { throw new Error('Key required'); }
if (!process.env.KEYB) { throw new Error('Configuration wrong'); }
if (!process.env.KEY) { throw new Error('Configuration wrong: KEY is missing in environment variables'); }
if (process.env.NODE_ENV != "production" && value == "test") {
return true; // dev testing convenience
}

const result = await compare(decodeURIComponent(value), process.env.KEYB);
const result = Buffer.from(encodeURIComponent(value)).toString('base64') == process.env.KEY;

if (!result) {
throw new Error('Key does not match');
Expand Down
4 changes: 2 additions & 2 deletions src/scripts/crypt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const compare = async function (password: string, hash: string) {
}

function pepper(password: string) {
const key = process.env.KEYA;
if (!key) { throw new Error('KEYA is not defined in the environment variables'); }
const key = process.env.KEY;
if (!key) { throw new Error('KEY is not defined in the environment variables'); }
return password + crypto.createHmac('sha256', key).digest("base64");
}
4 changes: 2 additions & 2 deletions src/scripts/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function cleanupCSRF() {
}

export function validateJWT(req: Request) {
const key = process.env.KEYA;
const key = process.env.KEY;
const header = req.header('Authorization');
const [type, token] = header ? header.split(' ') : "";
let payload: string | jwt.JwtPayload = "";
Expand Down Expand Up @@ -78,7 +78,7 @@ export function validateJWT(req: Request) {
}

export function createJWT(req: Request, res: Response) {
const key = process.env.KEYA;
const key = process.env.KEY;
if (!key) { throw new Error('Configuration is wrong'); }
const today = new Date();
const dateString = today.toLocaleDateString("de-DE", { weekday: "short", year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' });
Expand Down
1 change: 0 additions & 1 deletion src/tests/login.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ describe('Login', () => {
it('test invalid credentials to return error', async () => {
try {
userDataWithToken.csrfToken = csrfToken;
console.log("csrfToken %o", userDataWithToken.csrfToken);
await axios.post('http://localhost:80/login', qs.stringify(userDataWithToken));
} catch (error) {
const axiosError = error as AxiosError;
Expand Down
Loading