local app = require("lapis").Application() local db = require("lapis.db") local constants = require("constants") local bcrypt = require("bcrypt") local rand = require("openssl.rand") local models = require("models") local Users = models.Users local Sessions = models.Sessions local function authenticate_user(user, password) return bcrypt.verify(password, user.password_hash) end local function create_session_key() return rand.bytes(16):gsub(".", function(c) return string.format("%02x", string.byte(c)) end) end local function create_session(user_id) local days = 30 local expires_at = os.time() + (days * 24 * 60 * 60) return Sessions:create({ key = create_session_key(), user_id = user_id, expires_at = expires_at, }) end local function validate_session(session_key) local session = db.select('* FROM "sessions" WHERE "key" = ? AND "expires_at" > "?" LIMIT 1', session_key, os.time()) print(#session) if #session > 0 then return Users:find({id = session[1].user_id}) end return nil end local function validate_password(password) if #password < 10 or password:match("%s") then return false end if #password > 255 then return false end local r = password:match("%u+") and password:match("%l+") and password:match("%d+") and password:match("%p+") return r ~= nil and true end local function validate_username(username) if #username < 3 or #username > 20 then return false end return username:match("^[%w_-]+$") and true end app:get("user", "/:username", function(self) local user = Users:find({username = self.params.username}) if not user then return {status = 404} end if self.session.flash.just_logged_in then self.just_logged_in = true self.session.flash = {} end local me = validate_session(self.session.session_key) if not me and user.permission == constants.PermissionLevel.GUEST then return {status = 404} end self.user = user return {render = "user.user"} end) app:get("user_login", "/login", function(self) if self.session.session_key then local user = validate_session(self.session.session_key) if user ~= nil then return {redirect_to = self:url_for("user", {username = user.username})} end end if self.session.flash then self.err = self.session.flash.error self.session.flash = {} end return {render = "user.login"} end) app:post("user_login", "/login", function(self) if self.session.session_key then local user = validate_session(self.session.session_key) if user ~= nil then return {redirect_to = self:url_for("user", {username = user.username})} end end local username = self.params.username local password = self.params.password local user = Users:find({username = username}) if not user then self.session.flash = {error = "Invalid username or password"} return {redirect_to = self:url_for("user_login")} end if not authenticate_user(user, password) then self.session.flash = {error = "Invalid username or password"} return {redirect_to = self:url_for("user_login")} end local session = create_session(user.id) self.session.flash = {just_logged_in = true} self.session.session_key = session.key return {redirect_to = self:url_for("user", {username = username})} end) app:get("user_signup", "/signup", function(self) if self.session.session_key then local user = validate_session(self.session.session_key) if user ~= nil then return {redirect_to = self:url_for("user", {username = user.username})} end end if self.session.flash then self.err = self.session.flash.error self.session.flash = {} end return {render = "user.signup"} end) app:post("user_signup", "/signup", function(self) if self.session.session_key then local user = validate_session(self.session.session_key) if user ~= nil then return {redirect_to = self:url_for("user", {username = user.username})} end end local username = self.params.username local password = self.params.password local password2 = self.params.password2 local user = Users:find({username = username}) if user then self.session.flash = {error = "Username '" .. username .. "' is already taken."} return {redirect_to = self:url_for("user_signup")} end if not validate_username(username) then self.session.flash = {error = "Username must be 3-20 characters with only upper and lowercase letters, hyphens, and underscores."} return {redirect_to = self:url_for("user_signup")} end if not validate_password(password) then self.session.flash = {error = "Password must be 10+ chars with: 1 uppercase, 1 lowercase, 1 number, 1 special char, and no spaces."} return {redirect_to = self:url_for("user_signup")} end if password ~= password2 then self.session.flash = {error = "Passwords do not match."} return {redirect_to = self:url_for("user_signup")} end local new_user = Users:create({ username = username, password_hash = bcrypt.digest(password, constants.BCRYPT_ROUNDS), permission = constants.PermissionLevel.GUEST, }) local session = create_session(new_user.id) self.session.flash = {just_logged_in = true} self.session.session_key = session.key return {redirect_to = self:url_for("user", {username = username})} end) return app