feat(jwt): adding access and refresh token generation
This commit is contained in:
@ -12,5 +12,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Users struct {
|
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
|
postgres.Table
|
||||||
|
|
||||||
// Columns
|
// Columns
|
||||||
ID postgres.ColumnString
|
ID postgres.ColumnString
|
||||||
|
Email postgres.ColumnString
|
||||||
|
|
||||||
AllColumns postgres.ColumnList
|
AllColumns postgres.ColumnList
|
||||||
MutableColumns postgres.ColumnList
|
MutableColumns postgres.ColumnList
|
||||||
@ -59,15 +60,17 @@ func newUsersTable(schemaName, tableName, alias string) *UsersTable {
|
|||||||
func newUsersTableImpl(schemaName, tableName, alias string) usersTable {
|
func newUsersTableImpl(schemaName, tableName, alias string) usersTable {
|
||||||
var (
|
var (
|
||||||
IDColumn = postgres.StringColumn("id")
|
IDColumn = postgres.StringColumn("id")
|
||||||
allColumns = postgres.ColumnList{IDColumn}
|
EmailColumn = postgres.StringColumn("email")
|
||||||
mutableColumns = postgres.ColumnList{}
|
allColumns = postgres.ColumnList{IDColumn, EmailColumn}
|
||||||
|
mutableColumns = postgres.ColumnList{EmailColumn}
|
||||||
)
|
)
|
||||||
|
|
||||||
return usersTable{
|
return usersTable{
|
||||||
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
|
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
|
||||||
|
|
||||||
//Columns
|
//Columns
|
||||||
ID: IDColumn,
|
ID: IDColumn,
|
||||||
|
Email: EmailColumn,
|
||||||
|
|
||||||
AllColumns: allColumns,
|
AllColumns: allColumns,
|
||||||
MutableColumns: mutableColumns,
|
MutableColumns: mutableColumns,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -42,6 +44,7 @@ func (a *Auth) CreateCode(email string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) IsCodeValid(email string, code string) bool {
|
func (a *Auth) IsCodeValid(email string, code string) bool {
|
||||||
|
fmt.Println(a.codes)
|
||||||
existingCode, exists := a.codes[email]
|
existingCode, exists := a.codes[email]
|
||||||
if !exists {
|
if !exists {
|
||||||
return false
|
return false
|
||||||
@ -50,6 +53,16 @@ func (a *Auth) IsCodeValid(email string, code string) bool {
|
|||||||
return existingCode.Valid.After(time.Now()) && existingCode.Code == code
|
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 {
|
func CreateAuth(mailer Mailer) Auth {
|
||||||
return Auth{
|
return Auth{
|
||||||
codes: make(map[string]Code),
|
codes: make(map[string]Code),
|
||||||
|
@ -18,7 +18,7 @@ var testMailer = TestMail{}
|
|||||||
func TestCreateCode(t *testing.T) {
|
func TestCreateCode(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
auth := NewAuth(testMailer)
|
auth := CreateAuth(testMailer)
|
||||||
|
|
||||||
err := auth.CreateCode("test")
|
err := auth.CreateCode("test")
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/wneessen/go-mail"
|
"github.com/wneessen/go-mail"
|
||||||
@ -10,6 +11,8 @@ type MailClient struct {
|
|||||||
client *mail.Client
|
client *mail.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TestMailClient struct{}
|
||||||
|
|
||||||
type Mailer interface {
|
type Mailer interface {
|
||||||
SendCode(to string, code string) error
|
SendCode(to string, code string) error
|
||||||
}
|
}
|
||||||
@ -39,7 +42,18 @@ func (m MailClient) SendCode(to string, code string) error {
|
|||||||
return m.client.DialAndSend(msg)
|
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(
|
client, err := mail.NewClient(
|
||||||
"smtp.mailbox.org",
|
"smtp.mailbox.org",
|
||||||
mail.WithSMTPAuth(mail.SMTPAuthPlain),
|
mail.WithSMTPAuth(mail.SMTPAuthPlain),
|
||||||
|
@ -6,6 +6,7 @@ require (
|
|||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/go-chi/chi/v5 v5.2.1 // indirect
|
github.com/go-chi/chi/v5 v5.2.1 // indirect
|
||||||
github.com/go-jet/jet/v2 v2.12.0 // 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/google/uuid v1.6.0 // indirect
|
||||||
github.com/joho/godotenv v1.5.1 // indirect
|
github.com/joho/godotenv v1.5.1 // indirect
|
||||||
github.com/lib/pq v1.10.9 // 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-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 h1:z2JfvBAZgsfxlQz6NXBYdZTXc7ep3jhbszTLtETv1JE=
|
||||||
github.com/go-jet/jet/v2 v2.12.0/go.mod h1:ufQVRQeI1mbcO5R8uCEVcVf3Foej9kReBdwDx7YMWUM=
|
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/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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
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)
|
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.")
|
log.Println("Listening and serving on port 3040.")
|
||||||
if err := http.ListenAndServe(":3040", r); err != nil {
|
if err := http.ListenAndServe(":3040", r); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
|
@ -104,6 +104,15 @@ func (m UserModel) ListWithProperties(ctx context.Context, userId uuid.UUID) ([]
|
|||||||
return images, err
|
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 {
|
func NewUserModel(db *sql.DB) UserModel {
|
||||||
return UserModel{dbPool: db}
|
return UserModel{dbPool: db}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,8 @@ CREATE SCHEMA haystack;
|
|||||||
/* -----| Schema tables |----- */
|
/* -----| Schema tables |----- */
|
||||||
|
|
||||||
CREATE TABLE haystack.users (
|
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 (
|
CREATE TABLE haystack.image (
|
||||||
@ -164,7 +165,7 @@ EXECUTE PROCEDURE notify_new_image();
|
|||||||
/* -----| Test Data |----- */
|
/* -----| Test Data |----- */
|
||||||
|
|
||||||
-- Insert a user
|
-- 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 images
|
||||||
INSERT INTO haystack.image (id, image_name, image) VALUES
|
INSERT INTO haystack.image (id, image_name, image) VALUES
|
||||||
|
Reference in New Issue
Block a user