-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #20 from MaddyGuthridge/maddy-private-data-dir
Move private data to separate volume and modify firstrun workflow
- Loading branch information
Showing
70 changed files
with
844 additions
and
518 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
HOST=localhost # the hostname to use | ||
PORT=5096 # the port number to use | ||
DATA_REPO_PATH="./data" # the path to the data repository | ||
AUTH_SECRET="CHANGE ME" # the secret key to validate tokens | ||
HOST=localhost # the hostname to use | ||
PORT=5096 # the port number to use | ||
DATA_REPO_PATH="./data" # the path to the data volume | ||
PRIVATE_DATA_PATH="./private_data" # the path to the private data volume |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,8 @@ | |
|
||
# Data directory | ||
/data/ | ||
# Private data directory | ||
/private_data/ | ||
|
||
# Server logs | ||
*.log | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
"cSpell.words": [ | ||
"Asciinema", | ||
"firstrun", | ||
"Minifolio", | ||
"superstruct" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# File locations in Minifolio | ||
|
||
## Data directory | ||
|
||
Determined using environment variable `DATA_REPO_PATH`. | ||
|
||
Main portfolio data. Should be backed up using a `git` repo. | ||
|
||
### `config.json` | ||
|
||
Main site configuration. | ||
|
||
## Private data directory | ||
|
||
Determined using environment variable `PRIVATE_DATA_PATH`. | ||
|
||
Contains private data, including credentials and authentication secrets. | ||
|
||
### `config.local.json` | ||
|
||
Contains the local configuration of the server, including credentials and token | ||
info. | ||
|
||
### `id_ed25519`, `id_ed25519.pub` | ||
|
||
SSH key used by the server. These are used to perform git operations over SSH. | ||
|
||
### `auth.secret` | ||
|
||
Contains the authentication secret used by the server. This is used to validate | ||
JWTs. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/** | ||
* Minifolio Auth | ||
* | ||
* Code for performing authorization in Minifolio. | ||
* | ||
* If you discover a security vulnerability, please disclose it responsibly. | ||
*/ | ||
export { validateCredentials } from './passwords'; | ||
export { | ||
generateToken, | ||
validateTokenFromRequest, | ||
isRequestAuthorized, | ||
redirectOnInvalidToken, | ||
} from './tokens'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
|
||
import { hash } from 'crypto'; | ||
import { getLocalConfig } from '../data/localConfig'; | ||
import { error } from '@sveltejs/kit'; | ||
|
||
/** | ||
* How long to wait (in ms) before notifying the user that their login attempt | ||
* was rejected. | ||
*/ | ||
const FAIL_DURATION = 100; | ||
|
||
/** | ||
* Promise that resolves in a random amount of time, used to get some timing | ||
* invariance. | ||
*/ | ||
const sleepRandom = () => new Promise<void>((r) => setTimeout(r, Math.random() * FAIL_DURATION)); | ||
|
||
/** | ||
* Throw a 401 after a random (small) amount of time, so that timing attacks | ||
* cannot be used reliably. | ||
*/ | ||
async function fail(timer: Promise<void>, code: number) { | ||
await timer; | ||
return error(code, 'The username or password is incorrect'); | ||
} | ||
|
||
/** Hash a password with the given salt, returning the result */ | ||
export function hashAndSalt(salt: string, password: string): string { | ||
// TODO: Thoroughly check this against the OWASP guidelines -- it might | ||
// not match the requirements. | ||
// https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html | ||
return hash('SHA256', salt + password); | ||
} | ||
|
||
/** | ||
* Validate the user's credentials. | ||
* | ||
* If validation is successful, the user's userId is returned. | ||
* | ||
* If the credentials fail to validate, a random amount of time is waited | ||
* before sending the response, in order to prevent timing attacks. | ||
*/ | ||
export async function validateCredentials( | ||
username: string, | ||
password: string, | ||
code: number = 403, | ||
): Promise<string> { | ||
const local = await getLocalConfig(); | ||
|
||
const failTimer = sleepRandom(); | ||
|
||
// Find a user with a matching username | ||
const userId = Object.keys(local.auth).find(id => local.auth[id].username === username); | ||
|
||
if (!userId) { | ||
return fail(failTimer, code); | ||
} | ||
|
||
const hashResult = hashAndSalt(local.auth[userId].password.salt, password); | ||
|
||
if (hashResult !== local.auth[userId].password.hash) { | ||
return fail(failTimer, code); | ||
} | ||
return userId; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/** | ||
* Code for managing the authentication secret. | ||
*/ | ||
import fs from 'fs/promises'; | ||
import { getPrivateDataDir } from '../data/dataDir'; | ||
import { nanoid } from 'nanoid'; | ||
|
||
/** Returns the path to the auth secret file */ | ||
export function getAuthSecretPath(): string { | ||
return `${getPrivateDataDir()}/auth.secret`; | ||
} | ||
|
||
/** Cache for token secret */ | ||
let authSecret: string | undefined; | ||
|
||
/** Returns the secret value used to validate JWTs */ | ||
export async function getAuthSecret(): Promise<string> { | ||
if (authSecret) { | ||
return authSecret; | ||
} | ||
return fs.readFile(getAuthSecretPath(), { encoding: 'utf-8' }); | ||
} | ||
|
||
/** Generate and store a new auth secret, returning its value */ | ||
export async function generateAuthSecret(): Promise<string> { | ||
await fs.mkdir(getPrivateDataDir()).catch(() => { }); | ||
const secret = nanoid(); | ||
authSecret = secret; | ||
await fs.writeFile(getAuthSecretPath(), secret, { encoding: 'utf-8' }); | ||
return secret; | ||
} | ||
|
||
/** | ||
* Invalidate the in-memory copy of the auth secret. | ||
*/ | ||
export function invalidateAuthSecret() { | ||
authSecret = undefined; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/** | ||
* Code managing the setup of the auth system of Minifolio. | ||
*/ | ||
import { setLocalConfig, type ConfigLocalJson } from '../data/localConfig'; | ||
import { version } from '$app/environment'; | ||
import { hashAndSalt } from './passwords'; | ||
import { nanoid } from 'nanoid'; | ||
import { unixTime } from '$lib/util'; | ||
import { generateToken } from './tokens'; | ||
import type { Cookies } from '@sveltejs/kit'; | ||
import { generateAuthSecret } from './secret'; | ||
|
||
/** | ||
* Set up auth information. | ||
* | ||
* This is responsible for: | ||
* | ||
* 1. Setting up the auth secret | ||
* 2. Creating the first account | ||
* 3. Storing the auth info info the local config. | ||
*/ | ||
export async function authSetup( | ||
username: string, | ||
password: string, | ||
cookies?: Cookies, | ||
): Promise<string> { | ||
// 1. Set up the auth secret | ||
await generateAuthSecret(); | ||
|
||
// 2. Create the user | ||
const userId = nanoid(); | ||
|
||
// Generate a salt for the password | ||
// Using nanoid for secure generation | ||
const salt = nanoid(); | ||
const passwordHash = hashAndSalt(salt, password); | ||
|
||
// Set up auth config | ||
const config: ConfigLocalJson = { | ||
auth: { | ||
[userId]: { | ||
username, | ||
password: { | ||
hash: passwordHash, | ||
salt: salt, | ||
}, | ||
sessions: { | ||
notBefore: unixTime(), | ||
revokedSessions: {}, | ||
} | ||
}, | ||
}, | ||
version, | ||
}; | ||
await setLocalConfig(config); | ||
|
||
return generateToken(userId, cookies); | ||
} |
Oops, something went wrong.