feat(jwt): adding access and refresh token generation
This commit is contained in:
@ -12,5 +12,6 @@ import (
|
||||
)
|
||||
|
||||
type Users struct {
|
||||
ID uuid.UUID `sql:"primary_key"`
|
||||
ID uuid.UUID `sql:"primary_key"`
|
||||
Email string
|
||||
}
|
||||
|
@ -17,7 +17,8 @@ type usersTable struct {
|
||||
postgres.Table
|
||||
|
||||
// Columns
|
||||
ID postgres.ColumnString
|
||||
ID postgres.ColumnString
|
||||
Email postgres.ColumnString
|
||||
|
||||
AllColumns postgres.ColumnList
|
||||
MutableColumns postgres.ColumnList
|
||||
@ -59,15 +60,17 @@ func newUsersTable(schemaName, tableName, alias string) *UsersTable {
|
||||
func newUsersTableImpl(schemaName, tableName, alias string) usersTable {
|
||||
var (
|
||||
IDColumn = postgres.StringColumn("id")
|
||||
allColumns = postgres.ColumnList{IDColumn}
|
||||
mutableColumns = postgres.ColumnList{}
|
||||
EmailColumn = postgres.StringColumn("email")
|
||||
allColumns = postgres.ColumnList{IDColumn, EmailColumn}
|
||||
mutableColumns = postgres.ColumnList{EmailColumn}
|
||||
)
|
||||
|
||||
return usersTable{
|
||||
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
|
||||
|
||||
//Columns
|
||||
ID: IDColumn,
|
||||
ID: IDColumn,
|
||||
Email: EmailColumn,
|
||||
|
||||
AllColumns: allColumns,
|
||||
MutableColumns: mutableColumns,
|
||||
|
@ -1,6 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
@ -42,6 +44,7 @@ func (a *Auth) CreateCode(email string) error {
|
||||
}
|
||||
|
||||
func (a *Auth) IsCodeValid(email string, code string) bool {
|
||||
fmt.Println(a.codes)
|
||||
existingCode, exists := a.codes[email]
|
||||
if !exists {
|
||||
return false
|
||||
@ -50,6 +53,16 @@ func (a *Auth) IsCodeValid(email string, code string) bool {
|
||||
return existingCode.Valid.After(time.Now()) && existingCode.Code == code
|
||||
}
|
||||
|
||||
func (a *Auth) UseCode(email string, code string) error {
|
||||
if valid := a.IsCodeValid(email, code); !valid {
|
||||
fmt.Println("returning error?")
|
||||
return errors.New("This code is invalid.")
|
||||
}
|
||||
|
||||
delete(a.codes, email)
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateAuth(mailer Mailer) Auth {
|
||||
return Auth{
|
||||
codes: make(map[string]Code),
|
||||
|
@ -18,7 +18,7 @@ var testMailer = TestMail{}
|
||||
func TestCreateCode(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
auth := NewAuth(testMailer)
|
||||
auth := CreateAuth(testMailer)
|
||||
|
||||
err := auth.CreateCode("test")
|
||||
require.NoError(err)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/wneessen/go-mail"
|
||||
@ -10,6 +11,8 @@ type MailClient struct {
|
||||
client *mail.Client
|
||||
}
|
||||
|
||||
type TestMailClient struct{}
|
||||
|
||||
type Mailer interface {
|
||||
SendCode(to string, code string) error
|
||||
}
|
||||
@ -39,7 +42,18 @@ func (m MailClient) SendCode(to string, code string) error {
|
||||
return m.client.DialAndSend(msg)
|
||||
}
|
||||
|
||||
func CreateMailClient() (MailClient, error) {
|
||||
func (m TestMailClient) SendCode(to string, code string) error {
|
||||
fmt.Printf("Email: %s | Code %s\n", to, code)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateMailClient() (Mailer, error) {
|
||||
mode := os.Getenv("MODE")
|
||||
if mode == "DEV" {
|
||||
return TestMailClient{}, nil
|
||||
}
|
||||
|
||||
client, err := mail.NewClient(
|
||||
"smtp.mailbox.org",
|
||||
mail.WithSMTPAuth(mail.SMTPAuthPlain),
|
||||
|
@ -6,6 +6,7 @@ require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.1 // indirect
|
||||
github.com/go-jet/jet/v2 v2.12.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
|
@ -4,6 +4,8 @@ github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-jet/jet/v2 v2.12.0 h1:z2JfvBAZgsfxlQz6NXBYdZTXc7ep3jhbszTLtETv1JE=
|
||||
github.com/go-jet/jet/v2 v2.12.0/go.mod h1:ufQVRQeI1mbcO5R8uCEVcVf3Foej9kReBdwDx7YMWUM=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
|
61
backend/jwt.go
Normal file
61
backend/jwt.go
Normal file
@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type JwtType string
|
||||
|
||||
const (
|
||||
Access JwtType = "access"
|
||||
Refresh JwtType = "refresh"
|
||||
)
|
||||
|
||||
type JwtClaims struct {
|
||||
UserID string
|
||||
Type JwtType
|
||||
Expire time.Time
|
||||
}
|
||||
|
||||
func createToken(claims JwtClaims) *jwt.Token {
|
||||
return jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"UserID": claims.UserID,
|
||||
"Type": claims.Type,
|
||||
"Expire": claims.Expire,
|
||||
})
|
||||
}
|
||||
|
||||
func CreateRefreshToken(userId uuid.UUID) string {
|
||||
token := createToken(JwtClaims{
|
||||
UserID: userId.String(),
|
||||
Type: Refresh,
|
||||
Expire: time.Now().Add(time.Hour * 24 * 7),
|
||||
})
|
||||
|
||||
// TODO: bruh what is this
|
||||
tokenString, err := token.SignedString([]byte("very secret"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return tokenString
|
||||
}
|
||||
|
||||
func CreateAccessToken(userId uuid.UUID) string {
|
||||
token := createToken(JwtClaims{
|
||||
UserID: userId.String(),
|
||||
Type: Access,
|
||||
Expire: time.Now().Add(time.Hour),
|
||||
})
|
||||
|
||||
// TODO: bruh what is this
|
||||
tokenString, err := token.SignedString([]byte("very secret"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return tokenString
|
||||
}
|
@ -246,6 +246,62 @@ func main() {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
r.Post("/code", func(w http.ResponseWriter, r *http.Request) {
|
||||
type CodeBody struct {
|
||||
Email string `json:"email"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
type CodeReturn struct {
|
||||
Access string `json:"access"`
|
||||
Refresh string `json:"refresh"`
|
||||
}
|
||||
|
||||
codeBody := CodeBody{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&codeBody); err != nil {
|
||||
log.Println(err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprintf(w, "Request body was not correct")
|
||||
return
|
||||
}
|
||||
|
||||
if err := auth.UseCode(codeBody.Email, codeBody.Code); err != nil {
|
||||
log.Println(err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprintf(w, "email or code are incorrect")
|
||||
return
|
||||
}
|
||||
|
||||
uuid, err := userModel.GetUserIdFromEmail(r.Context(), codeBody.Email)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "Something went wrong.")
|
||||
return
|
||||
}
|
||||
|
||||
refresh := CreateRefreshToken(uuid)
|
||||
access := CreateAccessToken(uuid)
|
||||
|
||||
codeReturn := CodeReturn{
|
||||
Access: access,
|
||||
Refresh: refresh,
|
||||
}
|
||||
|
||||
json, err := json.Marshal(codeReturn)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "Something went wrong.")
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
|
||||
fmt.Fprint(w, string(json))
|
||||
})
|
||||
|
||||
log.Println("Listening and serving on port 3040.")
|
||||
if err := http.ListenAndServe(":3040", r); err != nil {
|
||||
log.Println(err)
|
||||
|
@ -104,6 +104,15 @@ func (m UserModel) ListWithProperties(ctx context.Context, userId uuid.UUID) ([]
|
||||
return images, err
|
||||
}
|
||||
|
||||
func (m UserModel) GetUserIdFromEmail(ctx context.Context, email string) (uuid.UUID, error) {
|
||||
getUserIdStmt := Users.SELECT(Users.ID).WHERE(Users.Email.EQ(String(email)))
|
||||
|
||||
user := model.Users{}
|
||||
err := getUserIdStmt.QueryContext(ctx, m.dbPool, &user)
|
||||
|
||||
return user.ID, err
|
||||
}
|
||||
|
||||
func NewUserModel(db *sql.DB) UserModel {
|
||||
return UserModel{dbPool: db}
|
||||
}
|
||||
|
@ -5,7 +5,8 @@ CREATE SCHEMA haystack;
|
||||
/* -----| Schema tables |----- */
|
||||
|
||||
CREATE TABLE haystack.users (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid()
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
email TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE haystack.image (
|
||||
@ -164,7 +165,7 @@ EXECUTE PROCEDURE notify_new_image();
|
||||
/* -----| Test Data |----- */
|
||||
|
||||
-- Insert a user
|
||||
INSERT INTO haystack.users (id) VALUES ('1db09f34-b155-4bf2-b606-dda25365fc89');
|
||||
INSERT INTO haystack.users (id, email) VALUES ('1db09f34-b155-4bf2-b606-dda25365fc89', 'me@email.com');
|
||||
|
||||
-- Insert images
|
||||
INSERT INTO haystack.image (id, image_name, image) VALUES
|
||||
|
Reference in New Issue
Block a user