Forget-Me-Not/helpers/auth.nim

149 lines
4.5 KiB
Nim

#[Copyright 2025 ITwrx.
This file is part of Simple Site Manager.
Simple Site Manager is released under the GNU Affero General Public License 3.0.
See COPYING or <https://www.gnu.org/licenses/> for details.]#
import std/[strutils, cgi, strtabs, cookies, sysrand, base64]
import guildenstern/[httpserver], sqliteral
import "../models/session", "db", "global"
#[type
Session* = object
sessionId*, csrfToken*: string
id*, userId*: int]#
type
User* = object
email*, password*: string
id*: int
proc getSessionBySessionId*(sessionId: string): Session =
{.gcsafe.}:
var session: Session
for row in db1.rows(SelectSessionBySessionId, sessionId):
session.id = row.getInt(0)
session.sessionId = row.getString(1)
session.userId = row.getInt(2)
session.csrfToken = row.getString(3)
return session
proc createUserSession*(userSession: Session) =
{.gcsafe.}:
db1.transaction:
discard db1.insert(InsertUserSession, userSession.sessionId, userSession.userId, userSession.csrfToken)
proc deleteSession*(sessionId: string) =
{.gcsafe.}:
db1.transaction:
db1.exec(DeleteSessionBySessionId, sessionId)
#Both form and auth have this template. The form.nim copy is exported.
template formInput(input: string): untyped =
readData(getBody()).getOrDefault(input)
proc getSessionIdFromCookies*(): string =
{.gcsafe.}:
let cookieString = http.headers.getOrDefault("cookie")
let allCookies = parseCookies(cookieString)
if allCookies.hasKey(APP_NAME & "_session"):
return allCookies[APP_NAME & "_session"]
else:
return ""
proc setVisitorCsrfToken*(): string =
{.gcsafe.}:
#create csrf token.
var csrfToken = $urandom(32)
csrfToken = base64.encode(csrfToken)
#delete old VisitorSessions, as they are just the one time use CSRF Tokens.
#may need to be redesigned for better multiuser robustness if it's deleting other users' unused tokens.
#deleteVisitorSessions()
#create session in db.
var visitorSession: Session
visitorSession.csrfToken = csrfToken
discard createVisitorSession(visitorSession)
return csrfToken
proc getUserPageCsrfToken*(): string =
{.gcsafe.}:
var sessionId: string
var userSession: Session
sessionId = getSessionIdFromCookies()
userSession = getSessionBySessionId(sessionId)
return userSession.csrfToken
proc newCsrfToken*(): string =
{.gcsafe.}:
#create csrf token.
var csrfToken = $urandom(32)
csrfToken = base64.encode(csrfToken)
return csrfToken
#[proc fCsrfToken*(): string =
{.gcsafe.}:
let sessionId = getSessionIdFromCookies()
let userSession = getUserSessionBySessionId(sessionId)
result = userSession.csrfToken]#
proc isValidVisitorCsrfToken*(csrfToken: string): bool =
#our previoulsy self-generated, valid csrfToken from the DB.
let visitorSessionCsrfToken = getSessionByCsrfToken(csrfToken).csrfToken
if visitorSessionCsrfToken.len > 0 and csrfToken == visitorSessionCsrfToken:
return true
else:
return false
proc isValidUserCsrfToken*(csrfToken: string): bool =
var sessionId: string
var userSession: Session
sessionId = getSessionIdFromCookies()
userSession = getSessionBySessionId(sessionId)
if userSession.csrfToken == csrfToken:
return true
else:
return false
proc isAuthdAdmin*(): bool =
{.gcsafe.}:
#get sessionId from request's cookie and see if it exists in session DB.
var sessionId: string
sessionId = getSessionIdFromCookies()
if sessionId.len() > 0:
var userSession: Session
try:
userSession = getSessionBySessionId(sessionId)
if userSession.id > 0:
return true
else:
return false
except Exception as e:
echo e.msg
return false
else:
return false
#adds CSRF checking for POST requests.
proc isAuthdAdminPost*(): bool =
{.gcsafe.}:
#get sessionId from request's cookie and see if it exists in session DB.
#var sessionId{.threadvar.}: string
var sessionId: string
sessionId = getSessionIdFromCookies()
if sessionId.len() > 0:
try:
let userSession = getSessionBySessionId(sessionId)
if userSession.id > 0:
return true
else:
return false
except Exception as e:
echo e.msg
return false
else:
return false
template ifAuthAdminPost*(procName: untyped): untyped =
if isAuthdAdminPost() == true:
procName
else:
reply(Http302, [location("/login")])