Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
bmerchant22 committed Jun 16, 2024
1 parent 95811f8 commit a1ae675
Show file tree
Hide file tree
Showing 23 changed files with 752 additions and 1 deletion.
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,21 @@
# fep-backend
# FEP - Backend

## Steps to run the project

1. Change secret.yml.template to secret.yml and enter the passwords for your email and db.

*NOTE*: Use iitk email address, and just add your username, for e.g. : bmerchant22, not [email protected]

2. Run ```go mod tidy```

3. Run the project:
``` go run cmd/main.go cmd/auth.go```
*NOTE*: Configs are set for /backend as root, so don't run ```go run main.go auth.go``` in cmd/

## Services

Currently, if you run the project, there are the following services running:

1. **Auth**: This service will be running on 8080 port of your machine by default, to change it, you can change the config, it has the following routes:
a. **/api/auth/sendotp (POST)**: If you hit this route with your user_id in payload (which is your roll no.), you will get an otp on your mail.
b. **/api/auth/signup (POST)**: If you hit this route with user_id, username and password, you will be signed up i.e. your details will be saved in the postgresql db running on your local
49 changes: 49 additions & 0 deletions auth/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package auth

import (
"os"

"github.com/sirupsen/logrus"
"github.com/spf13/viper"
_ "github.com/bmerchant22/hc_hackathon/config"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)

var db *gorm.DB
var logf *os.File

func openConnection() {
host := viper.GetString("DATABASE.HOST")
port := viper.GetString("DATABASE.PORT")
password := viper.GetString("DATABASE.PASSWORD")

dbName := viper.GetString("DBNAME.AUTH")
user := viper.GetString("DATABASE.USER")

dsn := "host=" + host + " user=" + user + " password=" + password
dsn += " dbname=" + dbName + " port=" + port + " sslmode=disable TimeZone=Asia/Kolkata"

database, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
// Logger: logger.Default.LogMode(logger.Error),
})
if err != nil {
logrus.Fatal("Failed to connect to auth database: ", err)
panic(err)
}

db = database

err = db.AutoMigrate(&User{}, &OTP{})
if err != nil {
logrus.Fatal("Failed to migrate auth database: ", err)
panic(err)
}

logrus.Info("Connected to auth database")
}

func init() {
openConnection()
go cleanupOTP()
}
34 changes: 34 additions & 0 deletions auth/db.otp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package auth

import (
"time"

"github.com/gin-gonic/gin"
"gorm.io/gorm"
)

func saveOTP(ctx *gin.Context, otp *OTP) error {
tx := db.WithContext(ctx).Create(&otp)
return tx.Error
}

func verifyOTP(ctx *gin.Context, userID string, otp string) (bool, error) {
var otpObj OTP
tx := db.WithContext(ctx).Where("user_id = ? AND otp = ? AND expires > ?", userID, otp, time.Now().UnixMilli()).First(&otpObj)
switch tx.Error {
case nil:
db.WithContext(ctx).Delete(&otpObj)
return true, nil
case gorm.ErrRecordNotFound:
return false, nil
default:
return false, tx.Error
}
}

func cleanupOTP() {
for {
db.Unscoped().Delete(OTP{}, "expires < ?", time.Now().Add(-24*time.Hour).UnixMilli())
time.Sleep(time.Hour * 24)
}
}
16 changes: 16 additions & 0 deletions auth/db.user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package auth

import "github.com/gin-gonic/gin"

func firstOrCreateUser(ctx *gin.Context, user *User) (uint, error) {
tx := db.WithContext(ctx).Create(user)
if tx.Error != nil {
tx = db.WithContext(ctx).Where("user_id = ?", user.UserID).Updates(user)
}
return user.ID, tx.Error
}

func fetchUser(ctx *gin.Context, user *User, userID string) error {
tx := db.WithContext(ctx).Where("user_id = ?", userID).First(&user)
return tx.Error
}
27 changes: 27 additions & 0 deletions auth/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package auth

import (
"sync"
"time"

"github.com/bmerchant22/hc_hackathon/constants"
"github.com/gorilla/websocket"
"gorm.io/gorm"
)

type User struct {
gorm.Model
UserID string `gorm:"uniqueIndex" json:"user_id"` // roll or PF number
Password string `json:"password"`
RoleID constants.Role `json:"role_id" gorm:"default:1"` // student role by default
ExpiryDate time.Time `json:"expiry_date"`
WSConnMux sync.Mutex `gorm:"-"`
WSConn *websocket.Conn `gorm:"-"` // WebSocket connection for the user
}

type OTP struct {
gorm.Model
UserID string `gorm:"column:user_id"`
OTP string `gorm:"column:otp"`
Expires uint `gorm:"column:expires"`
}
19 changes: 19 additions & 0 deletions auth/router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package auth

import (
"net/http"

"github.com/bmerchant22/hc_hackathon/mail"
"github.com/gin-gonic/gin"
)

func Router(mail_channel chan mail.Mail, r *gin.Engine) {
auth := r.Group("/api/auth")
{
auth.GET("/hello", func(ctx *gin.Context) {
ctx.JSON(http.StatusOK, gin.H{"msg": "hello"})
})
auth.POST("/otp", otpHandler(mail_channel))
auth.POST("/signup", signUpHandler(mail_channel))
}
}
52 changes: 52 additions & 0 deletions auth/user.signup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package auth

import (
"net/http"

"github.com/bmerchant22/hc_hackathon/mail"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)

type signUpRequest struct {
UserID string `json:"user_id" binding:"required"` // roll or PF number
Password string `json:"password" binding:"required"`
UserOTP string `json:"user_otp" binding:"required"`
}

func signUpHandler(mail_channel chan mail.Mail) gin.HandlerFunc {
return func(ctx *gin.Context) {
var signupReq signUpRequest

if err := ctx.ShouldBindJSON(&signupReq); err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

verified, err := verifyOTP(ctx, signupReq.UserID, signupReq.UserOTP)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if !verified {
ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid Roll No OTP"})
return
}

hashedPwd := hashAndSalt(signupReq.Password)

id, err := firstOrCreateUser(ctx, &User{
UserID: signupReq.UserID,
Password: hashedPwd,
})

if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

logrus.Infof("User %s created successfully with id %d", signupReq.UserID, id)
mail_channel <- mail.GenerateMail(signupReq.UserID, "Registered on HC Automation Portal", "Dear "+signupReq.UserID+",\n\nYou have been registered on HC Automation Portal")
ctx.JSON(http.StatusOK, gin.H{"status": "Successfully signed up"})
}
}
19 changes: 19 additions & 0 deletions auth/util.hash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package auth

import (
"github.com/sirupsen/logrus"
"golang.org/x/crypto/bcrypt"
)

func hashAndSalt(password string) string {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
logrus.Info(err)
}
return string(hash)
}

func comparePasswords(hashedPwd string, plainPwd string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hashedPwd), []byte(plainPwd))
return err == nil
}
57 changes: 57 additions & 0 deletions auth/util.otp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package auth

import (
"fmt"
"math/rand"
"net/http"
"time"

"github.com/bmerchant22/hc_hackathon/mail"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
_ "github.com/bmerchant22/hc_hackathon/config"
)

const charset = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"

var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano()))

var otpExpiration = viper.GetInt("OTP.EXPIRATION")
var size = viper.GetInt("OTP.SIZE")

type otpRequest struct {
UserID string `json:"user_id" binding:"required"`
}

func generateOTP() string {
b := make([]byte, size)
for i := range b {
b[i] = charset[seededRand.Intn(len(charset))]
}
return string(b)
}

func otpHandler(mail_channel chan mail.Mail) gin.HandlerFunc {
return func(ctx *gin.Context) {
var otpReq otpRequest
if err := ctx.ShouldBindJSON(&otpReq); err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

otp := generateOTP()

err := saveOTP(ctx, &OTP{
UserID: otpReq.UserID,
OTP: otp,
Expires: uint(time.Now().Add(time.Duration(otpExpiration) * time.Minute).UnixMilli()),
})
if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
mail_channel <- mail.GenerateMail(otpReq.UserID, "OTP", fmt.Sprintf("Dear %s,\n\nYour OTP is %s\nThis otp will expire in %d minutes", otpReq.UserID, otp, otpExpiration))

ctx.JSON(http.StatusOK, gin.H{"status": "OTP sent"})
}
}
31 changes: 31 additions & 0 deletions cmd/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package main

import (
"net/http"

"github.com/bmerchant22/hc_hackathon/auth"
_ "github.com/bmerchant22/hc_hackathon/config"
"github.com/bmerchant22/hc_hackathon/mail"
"github.com/bmerchant22/hc_hackathon/middleware"
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
)

func authServer(mail_channel chan mail.Mail) *http.Server {
PORT := viper.GetString("PORT.AUTH")
r := gin.New()
r.Use(middleware.CORS())
r.Use(gin.Recovery())
r.Use(gin.Logger())

auth.Router(mail_channel, r)

server := &http.Server{
Addr: ":" + PORT,
Handler: r,
ReadTimeout: readTimeout,
WriteTimeout: writeTimeout,
}

return server
}
33 changes: 33 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main

import (
"log"
"time"

"github.com/bmerchant22/hc_hackathon/mail"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
)

const (
readTimeout = 5 * time.Second
writeTimeout = 10 * time.Second
)

func main() {
var g errgroup.Group
mail_channel := make(chan mail.Mail)

gin.SetMode(gin.ReleaseMode)

go mail.Service(mail_channel)

g.Go(func() error {
return authServer(mail_channel).ListenAndServe()
})

log.Println("Auth server started")
if err := g.Wait(); err != nil {
log.Fatal(err)
}
}
16 changes: 16 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
DATABASE:
HOST: "localhost"
PORT: "5432"
USER: "postgres"
PORT:
AUTH: "8080"
APPOINTMENT: "8081"
MAIL:
HOST: "smtp.cc.iitk.ac.in"
PORT: "25"
DBNAME:
AUTH: "auth"
APPOINTMENT: "appointment"
OTP:
EXPIRATION: 20
SIZE: 6
6 changes: 6 additions & 0 deletions config/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package config

func init() {
viperConfig()
logrusConfig()
}
Loading

0 comments on commit a1ae675

Please sign in to comment.