#[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 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")])