73 lines
2.9 KiB
Nim
73 lines
2.9 KiB
Nim
import times
|
|
import strutils
|
|
import tiny_sqlite
|
|
import times
|
|
import options
|
|
import sequtils
|
|
import json
|
|
from os import `/`, existsOrCreateDir
|
|
import md5
|
|
import prologue
|
|
import logging
|
|
import nimcrypto/sysrand
|
|
import argon2_bind
|
|
|
|
func timeToTimestamp*(t: Time): int64 = toUnix(t) * 1000 + (nanosecond(t) div 1000000)
|
|
func timestampToTime*(ts: int64): Time = initTime(ts div 1000, (ts mod 1000) * 1000000)
|
|
func timestampToStr*(t: Time): string = intToStr(int(timeToTimestamp(t)))
|
|
proc toJsonHook(t: Time): JsonNode = newJInt(timeToTimestamp(t))
|
|
|
|
# store time as milliseconds
|
|
proc toDbValue*(t: Time): DbValue = DbValue(kind: sqliteInteger, intVal: timeToTimestamp(t))
|
|
proc fromDbValue*(value: DbValue, T: typedesc[Time]): Time = timestampToTime(value.intVal)
|
|
|
|
template autoInitializedThreadvar*(name: untyped, typ: typedesc, initialize: typed): untyped =
|
|
var data* {.threadvar.}: Option[typ]
|
|
proc `name`*(): typ =
|
|
if isSome(data): result = get data
|
|
else:
|
|
result = initialize
|
|
data = some result
|
|
|
|
proc randomID*(): int64 =
|
|
var arr: array[8, byte]
|
|
doAssert randomBytes(arr) == 8, "RNG failed"
|
|
cast[int64](arr).abs # TODO: do not invoke horrible unsafety?
|
|
|
|
# remove any unsafe characters from a filename, by only allowing bytes [a-zA-Z._ -]
|
|
proc normalizeFilename(s: string): string =
|
|
for byte in s:
|
|
if byte in {' ', 'a'..'z', 'A'..'Z', '-', '_', '.'}:
|
|
result.add(byte)
|
|
else:
|
|
result.add('_')
|
|
|
|
proc makeFilePath*(basepath, page, filename: string): string =
|
|
# putting tons of things into one directory may cause issues, so "shard" it into 256 subdirs deterministically
|
|
let pageHash = getMD5(page)
|
|
let hashdir = pageHash[0..1]
|
|
# it is possible that for some horrible reason someone could make two files/pages which normalize to the same thing
|
|
# but are nevertheless different files
|
|
# thus, put the hash of the ORIGINAL file/pagename before the normalized version
|
|
let pagedir = pageHash[2..31] & "-" & normalizeFilename(page)
|
|
let filenameHash = getMD5(filename)
|
|
discard existsOrCreateDir(basepath / hashdir)
|
|
discard existsOrCreateDir(basepath / hashdir / pagedir)
|
|
# saved file path should not include the basedir for file storage, as this may be moved around/reconfigured
|
|
hashdir / pagedir / (filenameHash & "-" & normalizeFilename(filename))
|
|
|
|
autoInitializedThreadvar(logger, ConsoleLogger, newConsoleLogger())
|
|
|
|
const argon2Params = setupArgon2Params(algoType = argon2_bind.Argon2id)
|
|
|
|
proc passwordHash*(pass: string): string =
|
|
var salt = repeat[byte](0, 16)
|
|
doAssert randomBytes(salt) == 16, "RNG failed"
|
|
argon2_bind.getOutput(pass, salt, argon2Params).encoded
|
|
|
|
proc passwordVerify*(pass: string, hash: string): bool =
|
|
try:
|
|
return argon2_bind.isVerified(hash, pass)
|
|
except Argon2Error:
|
|
logger().log(lvlWarn, "argon2 error", getCurrentExceptionMsg())
|
|
return false |