Skip to content
This repository has been archived by the owner on Dec 25, 2023. It is now read-only.

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
EloToJaa committed Jul 8, 2023
0 parents commit 93c2b7f
Show file tree
Hide file tree
Showing 37 changed files with 1,622 additions and 0 deletions.
44 changes: 44 additions & 0 deletions .air.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"

[build]
args_bin = []
bin = "tmp\\main.exe"
cmd = "go build -o ./tmp/main.exe ."
delay = 0
exclude_dir = ["assets", "tmp", "vendor", "testdata", "node_modules"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html", "env"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false

[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"

[log]
main_only = false
time = false

[misc]
clean_on_exit = false

[screen]
clear_on_rebuild = false
keep_scroll = true
40 changes: 40 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: goreleaser

on:
push:
tags:
- "*"

permissions:
contents: write
packages: write
issues: write

jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- run: git fetch --force --tags
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.20.5

- name: Log in to the Container registry
uses: docker/login-action@v2.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Run goreleaser
uses: goreleaser/goreleaser-action@v4
with:
distribution: goreleaser
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**/.env
/**/public/assets
/**/tmp
/**/*.exe
dist/
43 changes: 43 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json

# This is an example .goreleaser.yml file with some sensible defaults.
# Make sure to check the documentation at https://goreleaser.com
project_name: quizer
before:
hooks:
- go mod tidy
builds:
- main: .
env:
- CGO_ENABLED=0
goos:
- linux
- windows
goarch:
- amd64

release:
github:
owner: quizer-app
name: backend

dockers:
- goos: linux
goarch: amd64

image_templates:
- "ghcr.io/quizer-app/backend:latest"
- "ghcr.io/quizer-app/backend:{{ .Version }}"

dockerfile: Dockerfile
use: docker
build_flag_templates:
- "--platform=linux/amd64"
- "--pull"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"

push_flags:
- --tls-verify=false
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM alpine:3.18.2
COPY quizer /quizer
ENTRYPOINT ["/quizer"]
12 changes: 12 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package api

import (
v1 "github.com/EloToJaa/quizer/api/v1"
"github.com/gofiber/fiber/v2"
)

func RegisterRoutes(app *fiber.App) {
api := app.Group("/api")

v1.RegisterRoutes(api)
}
19 changes: 19 additions & 0 deletions api/v1/auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package auth

import (
"github.com/EloToJaa/quizer/controllers/auth"
"github.com/gofiber/fiber/v2"
)

func RegisterRoutes(router fiber.Router) {
authRouter := router.Group("/auth")

authRouter.Post("/login", auth.LoginController)
authRouter.Post("/register", auth.RegisterController)
authRouter.Post("/token", auth.TokenController)
authRouter.Delete("/logout", auth.LogoutController)
authRouter.Post("/verify", auth.VerifyController)
authRouter.Post("/verify/:id", auth.VerifyUserController)
authRouter.Post("/forgot-password", auth.ForgotPasswordController)
authRouter.Post("/reset-password/:id", auth.ResetPasswordController)
}
12 changes: 12 additions & 0 deletions api/v1/v1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package v1

import (
"github.com/EloToJaa/quizer/api/v1/auth"
"github.com/gofiber/fiber/v2"
)

func RegisterRoutes(router fiber.Router) {
v1 := router.Group("/v1")

auth.RegisterRoutes(v1)
}
56 changes: 56 additions & 0 deletions controllers/auth/forgotPasswordController.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package auth

import (
"github.com/EloToJaa/quizer/db"
"github.com/EloToJaa/quizer/enum"
"github.com/EloToJaa/quizer/models"
"github.com/EloToJaa/quizer/utils"
"github.com/gofiber/fiber/v2"
"go.mongodb.org/mongo-driver/bson"
"gopkg.in/go-playground/validator.v9"
)

type ForgotPasswordForm struct {
Email string `json:"email" validate:"required,email,min=3,max=64"`
}

func ForgotPasswordController(ctx *fiber.Ctx) error {
var body ForgotPasswordForm

// Parse body into struct
if err := ctx.BodyParser(&body); err != nil {
ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Bad request",
})
return err
}

// Validate body
validate := validator.New()
if err := validate.Struct(body); err != nil {
ctx.Status(fiber.StatusNotAcceptable).JSON(fiber.Map{
"message": "Validation failed",
"errors": utils.FormatValidationErrors(err),
})
return nil
}

userCollection := db.GetCollection(enum.Users)
userModel := &models.User{}

// Check if user exists
err := userCollection.FindOne(ctx.Context(), bson.M{"email": body.Email}).Decode(&userModel)
if err != nil {
ctx.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "User not found",
})
return nil
}

go utils.ResetPassword(userModel)
ctx.Status(fiber.StatusOK).JSON(fiber.Map{
"message": "Email sent",
})

return nil
}
127 changes: 127 additions & 0 deletions controllers/auth/loginController.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package auth

import (
"time"

"github.com/EloToJaa/quizer/db"
"github.com/EloToJaa/quizer/enum"
"github.com/EloToJaa/quizer/jwt"
"github.com/EloToJaa/quizer/models"
"github.com/EloToJaa/quizer/utils"
"github.com/gofiber/fiber/v2"
"go.mongodb.org/mongo-driver/bson"
"gopkg.in/go-playground/validator.v9"
)

type LoginForm struct {
UsernameOrEmail string `json:"usernameOrEmail" validate:"required,min=3,max=64"`
Password string `json:"password" validate:"required,min=8,max=64"`
}

func LoginController(ctx *fiber.Ctx) error {
// Get body from request; make username and email optional
var body LoginForm

// Parse body into struct
if err := ctx.BodyParser(&body); err != nil {
ctx.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"message": "Bad request",
})
return nil
}

// Validate body
validate := validator.New()
if err := validate.Struct(body); err != nil {
return ctx.Status(fiber.StatusNotAcceptable).JSON(fiber.Map{
"message": "Validation failed",
"errors": utils.FormatValidationErrors(err),
})
}

userCollection := db.GetCollection(enum.Users)
userModel := &models.User{}

// Check if user exists
err := userCollection.FindOne(ctx.Context(), bson.M{"$or": []bson.M{
{"username": body.UsernameOrEmail},
{"email": body.UsernameOrEmail},
}}).Decode(&userModel)
if err != nil {
return ctx.Status(fiber.StatusNotFound).JSON(fiber.Map{
"message": "Wrong username or password",
})
}

// Check if password is correct
argon2id := utils.NewArgon2ID()
if ok, err := argon2id.Verify(body.Password, userModel.Password); !ok || err != nil {
return ctx.Status(fiber.StatusNotFound).JSON(fiber.Map{
"message": "Wrong username or password",
})
}

// Check if user is verified
if !userModel.Verified {
return ctx.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"message": "User not verified",
})
}

// Generate JWT
user := utils.MapStructs(userModel, &jwt.User{}).(*jwt.User)
accessTokenData := &jwt.TokenData{
User: user,
ExpiresAt: jwt.GetAccessTokenExpirationTime().Unix(),
}
accessToken, err := accessTokenData.GenerateToken(jwt.GetAccessTokenSecret())
if err != nil {
ctx.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Internal server error",
})
return nil
}

refreshTokenData := &jwt.TokenData{
User: user,
ExpiresAt: jwt.GetRefreshTokenExpirationTime().Unix(),
}
refreshToken, err := refreshTokenData.GenerateToken(jwt.GetRefreshTokenSecret(userModel.Password))
if err != nil {
ctx.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Internal server error",
})
return nil
}

// Add refresh token to database
refreshTokenCollection := db.GetCollection(enum.RefreshTokens)
_, err = refreshTokenCollection.InsertOne(ctx.Context(), &models.RefreshToken{
UserId: userModel.Id,
Token: refreshToken,
UserPassword: userModel.Password,
CreatedAt: time.Now().Unix(),
ExpiresAt: jwt.GetRefreshTokenExpirationTime().Unix(),
})
if err != nil {
ctx.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"message": "Internal server error",
})
return nil
}

// Set cookie
ctx.Cookie(&fiber.Cookie{
Name: "refresh_token",
Value: refreshToken,
Expires: jwt.GetRefreshTokenExpirationTime(),
HTTPOnly: true,
})

// Send response
ctx.Status(fiber.StatusOK).JSON(fiber.Map{
"message": "Success",
"token": accessToken,
})
return nil
}
Loading

0 comments on commit 93c2b7f

Please sign in to comment.