-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
95811f8
commit a1ae675
Showing
23 changed files
with
752 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package config | ||
|
||
func init() { | ||
viperConfig() | ||
logrusConfig() | ||
} |
Oops, something went wrong.