Tested and created all endpoints, now entering production beta
This commit is contained in:
parent
c7bd5ad1f6
commit
6c46254988
|
@ -1 +1,3 @@
|
|||
.idea
|
||||
config.ini
|
||||
tmp/
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
secretkey: supersecretkey
|
||||
port: 8080
|
||||
host: localhost
|
||||
dblocation: /var/lib/maddy/credentials.db
|
||||
dblocation: tmp/credentials.db
|
250
main.go
250
main.go
|
@ -5,9 +5,13 @@ import (
|
|||
"crypto/rand"
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"centrifuge.hectabit.org/HectaBit/captcha"
|
||||
|
@ -19,7 +23,7 @@ import (
|
|||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
const salt_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
const salt_chars = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ23456789"
|
||||
|
||||
var DBLOCATION = "/var/lib/maddy/credentials.db"
|
||||
|
||||
|
@ -51,11 +55,11 @@ func computeBcrypt(cost int, pass string) (string, error) {
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(hash), nil
|
||||
return "bcrypt:" + string(hash), nil
|
||||
}
|
||||
|
||||
func verifyBcrypt(pass, hashSalt string) error {
|
||||
return bcrypt.CompareHashAndPassword([]byte(hashSalt), []byte(pass))
|
||||
func verifyBcrypt(hash, pass string) error {
|
||||
return bcrypt.CompareHashAndPassword([]byte(strings.TrimPrefix(hash, "bcrypt:")), []byte(pass))
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -99,7 +103,7 @@ func main() {
|
|||
c.Next()
|
||||
})
|
||||
|
||||
router.GET("/login", func(c *gin.Context) {
|
||||
router.GET("/signup", func(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
sessionid := genSalt(512)
|
||||
session.Options(sessions.Options{
|
||||
|
@ -107,7 +111,7 @@ func main() {
|
|||
})
|
||||
data, err := captcha.New(500, 100)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR] Failed to generate captcha at", time.Now().Unix(), err)
|
||||
fmt.Println("[ERROR] Failed to generate captcha at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||
c.String(500, "Failed to generate captcha")
|
||||
return
|
||||
}
|
||||
|
@ -115,39 +119,54 @@ func main() {
|
|||
session.Set("id", sessionid)
|
||||
err = session.Save()
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR] Failed to save session in /login at", time.Now().Unix(), err)
|
||||
fmt.Println("[ERROR] Failed to save session in /login at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||
c.String(500, "Failed to save session")
|
||||
return
|
||||
}
|
||||
var b64bytes bytes.Buffer
|
||||
err = data.WriteImage(&b64bytes)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR] Failed to encode captcha at", time.Now().Unix(), err)
|
||||
fmt.Println("[ERROR] Failed to encode captcha at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||
c.String(500, "Failed to encode captcha")
|
||||
return
|
||||
}
|
||||
c.HTML(200, "main.html", gin.H{
|
||||
c.HTML(200, "signup.html", gin.H{
|
||||
"captcha_image": base64.StdEncoding.EncodeToString(b64bytes.Bytes()),
|
||||
"unique_token": sessionid,
|
||||
})
|
||||
})
|
||||
|
||||
router.GET("/account", func(c *gin.Context) {
|
||||
loggedin, err := c.Cookie("loggedin")
|
||||
if errors.Is(err, http.ErrNoCookie) || loggedin != "true" {
|
||||
c.HTML(200, "login.html", gin.H{})
|
||||
return
|
||||
} else {
|
||||
c.HTML(200, "dashboard.html", gin.H{})
|
||||
}
|
||||
})
|
||||
|
||||
router.POST("/api/signup", func(c *gin.Context) {
|
||||
err := c.Request.ParseForm()
|
||||
var data map[string]interface{}
|
||||
err := c.ShouldBindJSON(&data)
|
||||
if err != nil {
|
||||
c.String(400, "Failed to parse form")
|
||||
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||
return
|
||||
}
|
||||
data := c.Request.Form
|
||||
session := sessions.Default(c)
|
||||
|
||||
if data.Get("captcha") != session.Get("captcha") {
|
||||
c.HTML(200, "badcaptcha.html", gin.H{})
|
||||
if data["unique_token"].(string) != session.Get("id") {
|
||||
c.HTML(403, "badtoken.html", gin.H{})
|
||||
return
|
||||
}
|
||||
|
||||
if data.Get("unique_token") != session.Get("id") {
|
||||
c.HTML(200, "badtoken.html", gin.H{})
|
||||
if data["captcha"].(string) != session.Get("captcha") {
|
||||
c.HTML(400, "badcaptcha.html", gin.H{})
|
||||
return
|
||||
}
|
||||
|
||||
if !regexp.MustCompile(`^[a-zA-Z0-9.]+$`).MatchString(data["username"].(string)) {
|
||||
c.String(402, "Invalid username")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -156,7 +175,7 @@ func main() {
|
|||
|
||||
err = session.Save()
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR] Failed to save session in /api/signup at", time.Now().Unix(), err)
|
||||
fmt.Println("[ERROR] Failed to save session in /api/signup at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||
c.String(500, "Failed to save session")
|
||||
return
|
||||
}
|
||||
|
@ -165,26 +184,211 @@ func main() {
|
|||
defer func(conn *sql.DB) {
|
||||
err := conn.Close()
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR] Failed to defer database connection at", time.Now().Unix(), err)
|
||||
fmt.Println("[ERROR] Failed to defer database connection in /api/signup at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||
c.String(500, "Failed to defer database connection")
|
||||
return
|
||||
}
|
||||
}(conn)
|
||||
|
||||
hashedpass, err := computeBcrypt(10, data.Get("password"))
|
||||
hashedpass, err := computeBcrypt(10, data["password"].(string))
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR] Failed to hash password connection at", time.Now().Unix(), err)
|
||||
fmt.Println("[ERROR] Failed to hash password in /api/signup at", time.Now().Unix(), err)
|
||||
c.String(500, "Failed to hash password")
|
||||
return
|
||||
}
|
||||
|
||||
_, err = conn.Exec("INSERT INTO passwords (key, value) VALUES (?, ?)", data.Get("username"), hashedpass)
|
||||
_, err = conn.Exec("INSERT INTO passwords (key, value) VALUES (?, ?)", data["username"].(string), hashedpass)
|
||||
if err != nil {
|
||||
c.HTML(500, "taken.html", gin.H{})
|
||||
c.String(501, "Username taken")
|
||||
return
|
||||
}
|
||||
|
||||
c.HTML(200, "postsignup.html", gin.H{})
|
||||
c.String(200, "Success")
|
||||
})
|
||||
|
||||
router.POST("/api/login", func(c *gin.Context) {
|
||||
var data map[string]interface{}
|
||||
err := c.ShouldBindJSON(&data)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||
return
|
||||
}
|
||||
|
||||
conn := get_db_connection()
|
||||
defer func(conn *sql.DB) {
|
||||
err := conn.Close()
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR] Failed to defer database connection at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||
c.String(500, "Failed to defer database connection")
|
||||
return
|
||||
}
|
||||
}(conn)
|
||||
|
||||
var rows string
|
||||
err = conn.QueryRow("SELECT value FROM passwords WHERE key = ?", data["username"].(string)).Scan(&rows)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
c.String(401, "Invalid username")
|
||||
return
|
||||
} else {
|
||||
fmt.Println("[ERROR] Failed to query database in /api/login at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||
c.String(500, "Failed to query database")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = verifyBcrypt(rows, data["password"].(string))
|
||||
if err != nil {
|
||||
c.String(403, "Password is incorrect")
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{"password": rows})
|
||||
})
|
||||
|
||||
router.POST("/api/deleteacct", func(c *gin.Context) {
|
||||
var data map[string]interface{}
|
||||
err := c.ShouldBindJSON(&data)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||
return
|
||||
}
|
||||
|
||||
conn := get_db_connection()
|
||||
defer func(conn *sql.DB) {
|
||||
err := conn.Close()
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR] Failed to defer database connection in /api/deleteacct at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||
c.String(500, "Failed to defer database connection")
|
||||
return
|
||||
}
|
||||
}(conn)
|
||||
|
||||
result, err := conn.Exec("DELETE FROM passwords WHERE key = ? AND value = ?", data["username"].(string), data["password"].(string))
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR] Failed to query database in /api/deleteacct at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||
c.String(500, "Failed to query database")
|
||||
} else {
|
||||
rowsaffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR] Failed to get rows affected in /api/deleteacct at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||
c.String(500, "Failed to get rows affected")
|
||||
} else {
|
||||
if rowsaffected == int64(0) {
|
||||
c.String(401, "Invalid username or password")
|
||||
} else {
|
||||
c.String(200, "Success")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
router.POST("/api/changepass", func(c *gin.Context) {
|
||||
var data map[string]interface{}
|
||||
err := c.ShouldBindJSON(&data)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||
return
|
||||
}
|
||||
|
||||
conn := get_db_connection()
|
||||
defer func(conn *sql.DB) {
|
||||
err := conn.Close()
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR] Failed to defer database connection in /api/changepass at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||
c.String(500, "Failed to defer database connection")
|
||||
return
|
||||
}
|
||||
}(conn)
|
||||
|
||||
newhash, err := computeBcrypt(10, data["newpass"].(string))
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR] Failed to hash password in /api/changepass at", time.Now().Unix(), err)
|
||||
c.String(500, "Failed to hash password")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := conn.Exec("UPDATE passwords SET value = ? WHERE key = ? AND value = ?", newhash, data["username"].(string), data["password"].(string))
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR] Failed to query database in /api/changepass at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||
c.String(500, "Failed to query database")
|
||||
} else {
|
||||
rowsaffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR] Failed to get rows affected in /api/changepass at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||
c.String(500, "Failed to get rows affected")
|
||||
} else {
|
||||
if rowsaffected == int64(0) {
|
||||
c.String(401, "Invalid username or password")
|
||||
} else {
|
||||
c.JSON(200, gin.H{"password": newhash})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
router.GET("/account/logout", func(c *gin.Context) {
|
||||
c.HTML(200, "logout.html", gin.H{})
|
||||
})
|
||||
|
||||
router.GET("/account/deleteacct", func(c *gin.Context) {
|
||||
c.HTML(200, "deleteacct.html", gin.H{})
|
||||
})
|
||||
|
||||
router.GET("/account/changepass", func(c *gin.Context) {
|
||||
c.HTML(200, "changepass.html", gin.H{})
|
||||
})
|
||||
|
||||
router.GET("/usererror", func(c *gin.Context) {
|
||||
c.HTML(200, "usererror.html", gin.H{})
|
||||
})
|
||||
|
||||
router.GET("/accounterror", func(c *gin.Context) {
|
||||
c.HTML(200, "accounterror.html", gin.H{})
|
||||
})
|
||||
|
||||
router.GET("/badcaptcha", func(c *gin.Context) {
|
||||
c.HTML(200, "badcaptcha.html", gin.H{})
|
||||
})
|
||||
|
||||
router.GET("/signuperror", func(c *gin.Context) {
|
||||
c.HTML(200, "signuperror.html", gin.H{})
|
||||
})
|
||||
|
||||
router.GET("/loginerror", func(c *gin.Context) {
|
||||
c.HTML(200, "loginerror.html", gin.H{})
|
||||
})
|
||||
|
||||
router.GET("/invalidtoken", func(c *gin.Context) {
|
||||
c.HTML(200, "invalidtoken.html", gin.H{})
|
||||
})
|
||||
|
||||
router.GET("/invaliduser", func(c *gin.Context) {
|
||||
c.HTML(200, "invaliduser.html", gin.H{})
|
||||
})
|
||||
|
||||
router.GET("/badpassword", func(c *gin.Context) {
|
||||
c.HTML(200, "badpassword.html", gin.H{})
|
||||
})
|
||||
|
||||
router.GET("/baduser", func(c *gin.Context) {
|
||||
c.HTML(200, "baduser.html", gin.H{})
|
||||
})
|
||||
|
||||
router.GET("/success", func(c *gin.Context) {
|
||||
c.HTML(200, "success.html", gin.H{})
|
||||
})
|
||||
|
||||
router.GET("/taken", func(c *gin.Context) {
|
||||
c.HTML(200, "taken.html", gin.H{})
|
||||
})
|
||||
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
c.HTML(200, "index.html", gin.H{})
|
||||
})
|
||||
|
||||
router.GET("/cta", func(c *gin.Context) {
|
||||
c.HTML(200, "cta.html", gin.H{})
|
||||
})
|
||||
|
||||
fmt.Println("[INFO] Server started at", time.Now().Unix())
|
||||
|
|
|
@ -20,8 +20,6 @@ body.darkmode {
|
|||
|
||||
input {
|
||||
padding: 10px;
|
||||
background-color: var(--button);
|
||||
color: white;
|
||||
border-style: none;
|
||||
border-radius: 5px;
|
||||
margin-top: 5px;
|
||||
|
@ -29,6 +27,11 @@ input {
|
|||
}
|
||||
|
||||
button {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
.button {
|
||||
text-decoration: none;
|
||||
padding: 10px;
|
||||
background-color: var(--button);
|
||||
border-style: none;
|
||||
|
@ -38,6 +41,19 @@ button {
|
|||
color: #ffffff;
|
||||
}
|
||||
|
||||
.logout {
|
||||
display: block;
|
||||
margin-top: 15px;
|
||||
width: 65px;
|
||||
margin-left: calc(50% - 45px);
|
||||
z-index: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background-color: var(--header);
|
||||
}
|
||||
|
||||
.pswdbox {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,18 @@ addEventListener("DOMContentLoaded", function () {
|
|||
if (getCookie("darkMode") === "true") {
|
||||
applyDarkMode();
|
||||
}
|
||||
|
||||
if (document.getElementById("username")) {
|
||||
document.getElementById("username").innerText = localStorage.getItem("user")
|
||||
}
|
||||
|
||||
if (document.getElementById("logout")) {
|
||||
document.getElementById("logout").innerText = "Currently logging out " + localStorage.getItem("user") + ", please be patient..."
|
||||
localStorage.removeItem("user")
|
||||
localStorage.removeItem("hash")
|
||||
setCookie("loggedin", "false", 1, "Strict")
|
||||
window.location.href = "/account"
|
||||
}
|
||||
})
|
||||
|
||||
function toggleDarkMode() {
|
||||
|
@ -48,3 +60,117 @@ function getCookie(name) {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function signup() {
|
||||
fetch("/api/signup", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
captcha: document.getElementById("captcha").value,
|
||||
unique_token: document.getElementById("unique_token").value,
|
||||
username: document.getElementById("username").value,
|
||||
password: document.getElementById("password").value
|
||||
})
|
||||
})
|
||||
.then((response) => response)
|
||||
.then((response) => {
|
||||
async function doStuff() {
|
||||
console.log(response.status)
|
||||
if (response.status === 200) {
|
||||
window.location.href = "/success"
|
||||
} else if (response.status === 501) {
|
||||
window.location.href = "/taken"
|
||||
} else if (response.status === 400) {
|
||||
window.location.href = "/badcaptcha"
|
||||
} else if (response.status === 402) {
|
||||
window.location.href = "/invaliduser"
|
||||
} else if (response.status === 403) {
|
||||
window.location.href = "/invalidtoken"
|
||||
} else {
|
||||
window.location.href = "/signuperror"
|
||||
}
|
||||
}
|
||||
doStuff()
|
||||
})
|
||||
}
|
||||
|
||||
function login() {
|
||||
let username = document.getElementById("username").value
|
||||
fetch("/api/login", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
username: username,
|
||||
password: document.getElementById("password").value
|
||||
})
|
||||
})
|
||||
.then((response) => response)
|
||||
.then((response) => {
|
||||
async function doStuff() {
|
||||
console.log(response.status)
|
||||
if (response.status === 200) {
|
||||
setCookie("loggedin", "true", 30, "Strict")
|
||||
const data = await response.json();
|
||||
localStorage.setItem("user", username)
|
||||
localStorage.setItem("hash", data.password)
|
||||
window.location.reload()
|
||||
} else if (response.status === 401) {
|
||||
window.location.href = "/baduser"
|
||||
} else if (response.status === 403) {
|
||||
window.location.href = "/badpassword"
|
||||
} else {
|
||||
window.location.href = "/loginerror"
|
||||
}
|
||||
}
|
||||
doStuff()
|
||||
})
|
||||
}
|
||||
|
||||
function deleteacct() {
|
||||
fetch("/api/deleteacct", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
username: localStorage.getItem("user"),
|
||||
password: localStorage.getItem("hash")
|
||||
})
|
||||
})
|
||||
.then((response) => response)
|
||||
.then((response) => {
|
||||
async function doStuff() {
|
||||
console.log(response.status)
|
||||
if (response.status === 200) {
|
||||
window.location.href = "/logout"
|
||||
} else if (response.status === 401) {
|
||||
window.location.href = "/usererror"
|
||||
} else {
|
||||
window.location.href = "/accounterror"
|
||||
}
|
||||
}
|
||||
doStuff()
|
||||
})
|
||||
}
|
||||
|
||||
function changepass() {
|
||||
fetch("/api/changepass", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
username: localStorage.getItem("user"),
|
||||
password: localStorage.getItem("hash"),
|
||||
newpass: document.getElementById("newpass").value
|
||||
})
|
||||
})
|
||||
.then((response) => response)
|
||||
.then((response) => {
|
||||
async function doStuff() {
|
||||
console.log(response.status)
|
||||
if (response.status === 200) {
|
||||
const data = await response.json();
|
||||
localStorage.setItem("hash", data.password)
|
||||
window.location.href = "/account"
|
||||
} else if (response.status === 401) {
|
||||
window.location.href = "/usererror"
|
||||
} else {
|
||||
window.location.href = "/accounterror"
|
||||
}
|
||||
}
|
||||
doStuff()
|
||||
})
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en"><head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<title>Sign Up</title>
|
||||
<link rel="stylesheet" href="/static/css/main.css">
|
||||
<script src="/static/js/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="headerbar">
|
||||
<a class="a" href="/">CtaMail</a>
|
||||
<a class="main a" href="/register">Sign up</a>
|
||||
<a class="a" href="/account">Account</a>
|
||||
<button class="a right" onclick="toggleDarkMode()">Toggle Theme</button>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h1>Register an Email Account</h1>
|
||||
<form method="POST" action="/api/signup">
|
||||
<label>Username</label>
|
||||
<div class="spacer">
|
||||
<input type="text" name="username" required="">
|
||||
</div>
|
||||
<br>
|
||||
<div class="Password">
|
||||
<label>Password</label>
|
||||
<div class="spacer">
|
||||
<input type="password" name="password" required="">
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<div class="spacer">
|
||||
<label>CAPTCHA</label>
|
||||
<div class="spacer">
|
||||
<img src="data:image/png;base64,{{ .captcha_image }}" alt="Captcha">
|
||||
</div>
|
||||
<div class="spacer">
|
||||
<input required="" name="captcha" type="text">
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<input type="hidden" name="unique_token" value="{{ .unique_token }}">
|
||||
<input type="submit" value="Register">
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue