clear out old janus stuff

main
Gabe Farrell 3 years ago
parent 9c06545931
commit c8697d6ef6

@ -6,12 +6,10 @@ require (
github.com/aws/aws-sdk-go-v2 v1.18.0
github.com/aws/aws-sdk-go-v2/credentials v1.13.23
github.com/go-chi/chi/v5 v5.0.8
github.com/golang-jwt/jwt/v5 v5.0.0
github.com/google/uuid v1.3.0
github.com/joho/godotenv v1.5.1
github.com/wagslane/go-password-validator v0.3.0
golang.org/x/crypto v0.8.0
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
)
require (

@ -36,8 +36,6 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
@ -55,8 +53,6 @@ github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSV
github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

@ -12,32 +12,23 @@ import (
"github.com/mnrva-dev/owltier.com/server/auth"
"github.com/mnrva-dev/owltier.com/server/config"
"github.com/mnrva-dev/owltier.com/server/db"
"github.com/mnrva-dev/owltier.com/server/token"
)
// TODO also write unit tests
type userdata struct {
Username string `json:"username"`
Password string `json:"password"`
Email string `json:"email"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
Name string `json:"name"`
Username string `json:"username"`
Password string `json:"password"`
}
var (
testuser = &db.UserSchema{
Username: "test",
Password: "testpassword1234!!",
Email: "test@example.com",
}
permatestuser = &db.UserSchema{
Id: "0a85b0ae-a577-4be3-8609-d443d50f6939",
Username: "user",
Password: "password1234!!",
Email: "user@example.com",
Refresh: "",
}
)
@ -53,7 +44,7 @@ func TestRegister(t *testing.T) {
ts := runTestServer()
defer ts.Close()
data := url.Values{}
data.Set("email", testuser.Email)
data.Set("username", testuser.Username)
if config.UsernamesEnabled() {
data.Set("username", testuser.Username)
}
@ -71,29 +62,13 @@ func TestRegister(t *testing.T) {
fmt.Println(string(body))
t.FailNow()
}
AccessToken := strings.Split(string(body), "\n")[0]
RefreshToken := strings.Split(string(body), "\n")[1]
if AccessToken == "" || RefreshToken == "" {
t.Errorf("Expected access and refresh token to be set")
}
if AccessToken == RefreshToken {
t.Errorf("Expected access and refresh tokens to be different")
}
_, err = token.ValidateAccess(AccessToken)
if err != nil {
t.Errorf("Expected valid access token, got %v", err)
}
_, err = token.ValidateRefresh(RefreshToken)
if err != nil {
t.Errorf("Expected valid refresh token, got %v", err)
}
}
func TestLogin(t *testing.T) {
ts := runTestServer()
defer ts.Close()
data := url.Values{}
data.Set("email", testuser.Email)
data.Set("username", testuser.Username)
if config.UsernamesEnabled() {
data.Set("username", testuser.Username)
}
@ -111,104 +86,13 @@ func TestLogin(t *testing.T) {
fmt.Println(string(body))
t.FailNow()
}
AccessToken := strings.Split(string(body), "\n")[0]
RefreshToken := strings.Split(string(body), "\n")[1]
if AccessToken == "" || RefreshToken == "" {
t.Errorf("Expected access and refresh token to be set")
}
if AccessToken == RefreshToken {
t.Errorf("Expected access and refresh tokens to be different")
}
_, err = token.ValidateAccess(AccessToken)
if err != nil {
t.Errorf("Expected valid access token, got %v", err)
}
_, err = token.ValidateRefresh(RefreshToken)
if err != nil {
t.Errorf("Expected valid refresh token, got %v", err)
}
}
func TestRefresh(t *testing.T) {
ts := runTestServer()
defer ts.Close()
data := url.Values{}
data.Set("email", testuser.Email)
data.Set("password", testuser.Password)
resp, err := http.PostForm(fmt.Sprintf("%s/login", ts.URL), data)
if err != nil {
t.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal("Could not read body")
}
if resp.StatusCode/100 != 2 {
t.Error("Failed to login")
t.FailNow()
}
RefreshToken := strings.Split(string(body), "\n")[1]
req, err := http.NewRequest("POST", fmt.Sprintf("%s/token/refresh", ts.URL), nil)
if err != nil {
t.Fatalf("Couldn't create request: %v", err)
}
req.Header.Set("Authorization", "Bearer "+RefreshToken)
resp, err = http.DefaultClient.Do(req)
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal("Could not read body")
}
if resp.StatusCode/100 != 2 {
t.Errorf("%s\n", string(body))
t.Fatalf("Expected token to be validated but got status %d", resp.StatusCode)
}
AccessToken := strings.Split(string(body), "\n")[0]
RefreshToken = strings.Split(string(body), "\n")[1]
if AccessToken == "" || RefreshToken == "" {
t.Errorf("Expected access and refresh token to be set")
}
if AccessToken == RefreshToken {
t.Errorf("Expected access and refresh tokens to be different")
}
_, err = token.ValidateAccess(AccessToken)
if err != nil {
t.Errorf("Expected valid access token, got %v", err)
}
_, err = token.ValidateRefresh(RefreshToken)
if err != nil {
t.Errorf("Expected valid refresh token, got %v", err)
}
}
func TestValidateAccess(t *testing.T) {
ts := runTestServer()
defer ts.Close()
AccessToken := token.GenerateAccess(testuser)
req, err := http.NewRequest("GET", fmt.Sprintf("%s/token/validate", ts.URL), nil)
if err != nil {
t.Fatalf("Couldn't create request: %v", err)
}
req.Header.Set("Authorization", "Bearer "+AccessToken)
resp, err := http.DefaultClient.Do(req)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatal("Could not read body")
}
if err != nil {
t.Error(string(body))
t.Fatal(err)
}
if resp.StatusCode/100 != 2 {
t.Error(string(body))
t.Errorf("Expected token to be validated but got status %d", resp.StatusCode)
}
}
func TestDeleteAccount(t *testing.T) {
ts := runTestServer()
defer ts.Close()
data := url.Values{}
data.Set("email", testuser.Email)
data.Set("username", testuser.Username)
data.Set("password", testuser.Password)
resp, err := http.PostForm(fmt.Sprintf("%s/login", ts.URL), data)
if err != nil {
@ -222,6 +106,7 @@ func TestDeleteAccount(t *testing.T) {
t.Error("Failed to login")
t.FailNow()
}
// TODO: Fix this so that it uses session token
AccessToken := strings.Split(string(body), "\n")[0]
data = url.Values{}
data.Set("password", testuser.Password)

@ -0,0 +1,5 @@
package auth
const (
SESSION_COOKIE = "_owltier.com_sess"
)

@ -6,7 +6,6 @@ import (
"github.com/mnrva-dev/owltier.com/server/db"
"github.com/mnrva-dev/owltier.com/server/jsend"
"github.com/mnrva-dev/owltier.com/server/middleware"
"golang.org/x/crypto/bcrypt"
)
@ -15,10 +14,23 @@ func DeleteAccount(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
password := strings.TrimSpace(r.FormValue("password"))
// get user from token parse middleware
user, err := r.Context().Value(middleware.ContextKeyValues).(*middleware.Values).GetUser()
// get user session id from cookies
sessC, err := r.Cookie(SESSION_COOKIE)
if err != nil {
jsend.Error(w, "Failed to retrieve user information")
jsend.Fail(w, 401, map[string]interface{}{
"session": "invalid session",
})
return
}
session := sessC.Value
var user = &db.UserSchema{}
err = db.FetchByGsi(&db.UserSchema{
Session: session,
}, user)
if err != nil {
jsend.Fail(w, 401, map[string]interface{}{
"session": "invalid session",
})
return
}

@ -1,10 +1,8 @@
package auth
import (
"encoding/base64"
"errors"
"net/http"
"net/mail"
"regexp"
"strings"
@ -17,11 +15,8 @@ const (
)
type RequestForm struct {
Username string
Password string
Email string
Redirect bool
RedirectUrl string
Username string
Password string
}
func (h *RequestForm) validate() error {
@ -31,23 +26,6 @@ func (h *RequestForm) validate() error {
if h.Password == "" {
return errors.New("password is required")
}
if h.Email == "" {
return errors.New("email is required")
}
if _, err := mail.ParseAddress(h.Email); err != nil {
return errors.New("email address is not valid")
}
if h.Redirect {
if h.RedirectUrl == "" {
return errors.New("redirect url is required")
}
var url []byte
_, err := base64.NewDecoder(base64.StdEncoding, strings.NewReader(h.RedirectUrl)).Read(url)
if err != nil {
return err
}
h.RedirectUrl = string(url)
}
if !regexp.MustCompile(`^[a-zA-Z0-9-_]{3,24}$`).MatchString(h.Username) && config.UsernamesEnabled() {
return errors.New("username is not valid")
}
@ -68,9 +46,6 @@ func (h *RequestForm) Parse(r *http.Request) error {
if len(h.Password) > 128 {
h.Password = h.Password[:128]
}
h.Email = strings.TrimSpace(r.FormValue("email"))
h.Redirect = r.FormValue("redirect") != "" || strings.ToLower(r.FormValue("redirect")) == "false"
h.RedirectUrl = strings.TrimSpace(r.FormValue("redirect_url"))
return h.validate()
}

@ -4,9 +4,9 @@ import (
"net/http"
"time"
"github.com/google/uuid"
"github.com/mnrva-dev/owltier.com/server/db"
"github.com/mnrva-dev/owltier.com/server/jsend"
"github.com/mnrva-dev/owltier.com/server/token"
"golang.org/x/crypto/bcrypt"
)
@ -19,44 +19,35 @@ func Login(w http.ResponseWriter, r *http.Request) {
// get user from DB
var user = &db.UserSchema{}
err := db.FetchByGsi(&db.UserSchema{
Email: form.Email,
err := db.Fetch(&db.UserSchema{
Username: form.Username,
}, user)
if err != nil {
jsend.Fail(w, 401, map[string]interface{}{
"message": "Email or password is invalid"})
"message": "Username or password is invalid"})
return
}
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(form.Password))
if err != nil {
jsend.Fail(w, 401, map[string]interface{}{
"message": "Email or password is invalid"})
"message": "Username or password is invalid"})
return
}
// prepare login information for the client
accessT := token.GenerateAccess(user)
refreshT := token.GenerateRefresh(user)
db.Update(user, "refresh_token", refreshT)
session := uuid.NewString()
db.Update(user, "session", session)
db.Update(user, "last_login_at", time.Now().Unix())
http.SetCookie(w, &http.Cookie{
Name: "_owltier.com_auth",
Value: accessT,
Path: "/",
Expires: time.Now().Add(time.Hour),
HttpOnly: true,
Secure: true,
})
http.SetCookie(w, &http.Cookie{
Name: "_owltier.com_refresh",
Value: accessT,
Name: SESSION_COOKIE,
Value: session,
Path: "/",
Expires: time.Now().Add(time.Hour),
HttpOnly: true,
Secure: true,
})
jsend.Success(w, map[string]interface{}{
"id": user.Id,
"username": user.Username,
})
}

@ -7,7 +7,6 @@ import (
"github.com/google/uuid"
"github.com/mnrva-dev/owltier.com/server/db"
"github.com/mnrva-dev/owltier.com/server/jsend"
"github.com/mnrva-dev/owltier.com/server/token"
"golang.org/x/crypto/bcrypt"
)
@ -20,8 +19,8 @@ func Register(w http.ResponseWriter, r *http.Request) {
// get user from DB
var user = &db.UserSchema{}
err := db.FetchByGsi(&db.UserSchema{
Email: form.Email,
err := db.Fetch(&db.UserSchema{
Username: form.Username,
}, user)
// if we didnt get NotFound error...
if err == nil {
@ -39,20 +38,10 @@ func Register(w http.ResponseWriter, r *http.Request) {
return
}
user.Password = string(hashedPassword)
user.Email = form.Email
user.Id = uuid.New().String()
user.Scope = "default"
user.EmailIsVerified = false
accessT := token.GenerateAccess(user)
refreshT := token.GenerateRefresh(user)
if accessT == "" || refreshT == "" {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
session := uuid.NewString()
user.Refresh = refreshT
user.Session = session
err = db.Create(user)
if err != nil {
@ -62,26 +51,14 @@ func Register(w http.ResponseWriter, r *http.Request) {
}
http.SetCookie(w, &http.Cookie{
Name: "_owltier_auth",
Value: accessT,
Name: SESSION_COOKIE,
Value: session,
Path: "/",
Expires: time.Now().Add(time.Hour),
HttpOnly: true,
Secure: true,
})
http.SetCookie(w, &http.Cookie{
Name: "_owltier_refresh",
Value: accessT,
Path: "/",
Expires: time.Now().Add(time.Hour),
HttpOnly: true,
Secure: true,
jsend.Success(w, map[string]interface{}{
"username": user.Username,
})
if form.Redirect {
http.Redirect(w, r, form.RedirectUrl, 303)
} else {
jsend.Success(w, map[string]interface{}{
"id": user.Id,
})
}
}

@ -2,7 +2,6 @@ package auth
import (
"github.com/go-chi/chi/v5"
"github.com/mnrva-dev/owltier.com/server/middleware"
)
func BuildRouter() *chi.Mux {
@ -10,15 +9,7 @@ func BuildRouter() *chi.Mux {
r.Post("/login", Login)
r.Post("/register", Register)
r.Group(func(r chi.Router) {
r.Use(middleware.TokenValidater)
r.Post("/delete", DeleteAccount)
})
r.Group(func(r chi.Router) {
r.Use(middleware.RefreshValidator)
r.Post("/token/refresh", Refresh)
})
r.Get("/token/validate", Validate)
r.Post("/delete", DeleteAccount)
return r
}

@ -1,85 +0,0 @@
package auth
import (
"fmt"
"net/http"
"strings"
"time"
"github.com/mnrva-dev/owltier.com/server/db"
"github.com/mnrva-dev/owltier.com/server/jsend"
"github.com/mnrva-dev/owltier.com/server/middleware"
"github.com/mnrva-dev/owltier.com/server/token"
)
func Refresh(w http.ResponseWriter, r *http.Request) {
// get user and token from token parse middleware
user, err := r.Context().Value(middleware.ContextKeyValues).(*middleware.Values).GetUser()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
t, err := r.Context().Value(middleware.ContextKeyValues).(*middleware.Values).GetRefreshToken()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if user.Refresh != t {
http.Error(w, "Token mismatch", http.StatusUnauthorized)
fmt.Printf("%s\n%s", user.Refresh, t)
return
}
// prepare login information for the client
accessT := token.GenerateAccess(user)
refreshT := token.GenerateRefresh(user)
db.Update(user, "RefreshToken", refreshT)
http.SetCookie(w, &http.Cookie{
Name: "_owltier.com_auth",
Value: accessT,
Path: "/",
Expires: time.Now().Add(time.Hour),
HttpOnly: true,
Secure: true,
})
http.SetCookie(w, &http.Cookie{
Name: "_owltier.com_refresh",
Value: accessT,
Path: "/",
Expires: time.Now().Add(time.Hour),
HttpOnly: true,
Secure: true,
})
jsend.Success(w, nil)
}
func Validate(w http.ResponseWriter, r *http.Request) {
header := r.Header.Get("Authorization")
headerVals := strings.Split(header, " ")
if strings.ToLower(headerVals[0]) != "bearer" {
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprint(w, "Bad Authorization Scheme")
}
t := headerVals[1]
if t == "" {
w.WriteHeader(400)
fmt.Fprint(w, "No Token Provided")
return
}
claims, err := token.ValidateAccess(t)
if err != nil {
w.WriteHeader(401)
fmt.Fprint(w, "Unauthorized")
return
}
if claims.Type != "Access" {
w.WriteHeader(401)
fmt.Fprint(w, "Unauthorized")
return
}
jsend.Success(w, nil)
}

@ -9,7 +9,6 @@ import (
func TestCreate(t *testing.T) {
u := &db.UserSchema{
Username: "myuser",
Email: "me@example.com",
Password: "secret123",
}
@ -27,28 +26,28 @@ func TestRead(t *testing.T) {
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if u.Email != "me@example.com" {
t.Fatalf("Expected correct email, got %v", u.Email)
}
}
func TestGsiRead(t *testing.T) {
u := &db.UserSchema{
Email: "me@example.com",
}
err := db.FetchByGsi(u, u)
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if u.Username != "myuser" {
t.Fatalf("Expected correct username, got %v", u.Email)
t.Fatalf("Expected correct username, got %v", u.Username)
}
}
// TODO: Fix it
// func TestGsiRead(t *testing.T) {
// u := &db.UserSchema{
// Session: "1234",
// }
// err := db.FetchByGsi(u, u)
// if err != nil {
// t.Fatalf("Expected no error, got %v", err)
// }
// if u.Username != "myuser" {
// t.Fatalf("Expected correct username, got %v", u.Username)
// }
// }
func TestDelete(t *testing.T) {
u := &db.UserSchema{
Username: "myuser",
Email: "me@example.com",
Password: "secret123",
}
err := db.Delete(u)

@ -5,23 +5,18 @@ import (
)
type UserSchema struct {
Pk string `dynamodbav:"pk"`
Gsi1pk string `dynamodbav:"gsi1pk"`
Id string `dynamodbav:"id"`
Username string `dynamodbav:"username,omitempty"`
Email string `dynamodbav:"email"`
EmailIsVerified bool `dynamodbav:"email_is_verified"`
Password string `dynamodbav:"password"`
Refresh string `dynamodbav:"refresh_token"`
Scope string `dynamodbav:"scope"`
Policies []string `dynamodbav:"policies"`
CreatedAt int64 `dynamodbav:"created_at"`
LastLoginAt int64 `dynamodbav:"last_login_at"`
Pk string `dynamodbav:"pk"`
Gsi1pk string `dynamodbav:"gsi1pk"`
Session string `dynamodbav:"session"`
Username string `dynamodbav:"username"`
Password string `dynamodbav:"password"`
CreatedAt int64 `dynamodbav:"created_at"`
LastLoginAt int64 `dynamodbav:"last_login_at"`
}
func (u *UserSchema) buildKeys() {
u.Pk = "user#" + u.Id
u.Gsi1pk = "email#" + u.Email
u.Pk = "user#" + u.Username
u.Gsi1pk = "session#" + u.Session
}
func (u *UserSchema) getKey() map[string]types.AttributeValue {

@ -0,0 +1,12 @@
package list
type List struct {
Id string `json:"id"`
CreatedAt int64 `json:"created_at"`
CreatedBy string `json:"created_by"`
Format string `json:"format"`
Breaks []bool `json:"breaks"`
NA []string `json:"na"`
APAC []string `json:"apac"`
Combined []string `json:"combined"`
}

@ -1,9 +0,0 @@
package middleware
type ContextKey string
const (
ContextKeyUser = ContextKey("user")
ContextKeyToken = ContextKey("token")
ContextKeyValues = ContextKey("values")
)

@ -1,138 +0,0 @@
package middleware
import (
"context"
"errors"
"fmt"
"log"
"net/http"
"strings"
"github.com/mnrva-dev/owltier.com/server/db"
"github.com/mnrva-dev/owltier.com/server/token"
)
type Values struct {
m map[string]interface{}
}
func (v Values) GetUser() (*db.UserSchema, error) {
u, ok := v.m["user"].(*db.UserSchema)
if !ok || u == nil {
return nil, errors.New("user is not set")
}
return u, nil
}
func (v Values) GetAccessToken() (string, error) {
u, ok := v.m["access"].(string)
if !ok {
return "", errors.New("access token is not set")
}
return u, nil
}
func (v Values) GetRefreshToken() (string, error) {
u, ok := v.m["refresh"].(string)
if !ok {
return "", errors.New("refresh token is not set")
}
return u, nil
}
func TokenValidater(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
header := r.Header.Get("Authorization")
headerVals := strings.Split(header, " ")
if strings.ToLower(headerVals[0]) != "bearer" {
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprint(w, "Bad Authorization Scheme")
}
t := headerVals[1]
if t == "" {
w.WriteHeader(401)
fmt.Fprint(w, "No Token Provided")
return
}
claims, err := token.ValidateAccess(t)
if err != nil {
w.WriteHeader(401)
fmt.Fprint(w, "Unauthorized")
return
}
if claims.Type != token.TypeAccess {
w.WriteHeader(401)
fmt.Fprint(w, "Unauthorized")
return
}
var user = &db.UserSchema{}
err = db.Fetch(&db.UserSchema{Id: claims.Id}, user)
if err != nil {
log.Println(err)
w.WriteHeader(400)
fmt.Fprint(w, "User Not Found With Id: "+claims.Id)
return
}
v := Values{map[string]interface{}{
"user": user,
"access": t,
}}
ctx := context.WithValue(r.Context(), ContextKeyValues, &v)
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
func RefreshValidator(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
header := r.Header.Get("Authorization")
headerVals := strings.Split(header, " ")
if strings.ToLower(headerVals[0]) != "bearer" {
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprint(w, "Bad Authorization Scheme")
}
t := headerVals[1]
if t == "" {
w.WriteHeader(401)
fmt.Fprint(w, "No Token Provided")
return
}
claims, err := token.ValidateRefresh(t)
if err != nil {
w.WriteHeader(401)
fmt.Fprint(w, "Unauthorized")
return
}
if claims.Type != token.TypeRefresh {
w.WriteHeader(401)
fmt.Fprint(w, "Unauthorized")
return
}
var user = &db.UserSchema{}
err = db.Fetch(&db.UserSchema{Id: claims.Id}, user)
if err != nil {
w.WriteHeader(400)
fmt.Fprint(w, "User Not Found")
return
}
v := Values{map[string]interface{}{
"user": user,
"refresh": t,
}}
ctx := context.WithValue(r.Context(), ContextKeyValues, &v)
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}

@ -1,43 +0,0 @@
package token
import (
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"github.com/mnrva-dev/owltier.com/server/config"
"github.com/mnrva-dev/owltier.com/server/db"
)
type Claims struct {
jwt.RegisteredClaims
Id string `json:"id"`
Username string `json:"username,omitempty"`
Email string `json:"email"`
EmailIsVerified bool `json:"email_verified"`
XSRF string `json:"xsrf"`
Role string `json:"role,omitempty"`
Policies []string `json:"policies,omitempty"`
Type string `json:"type"`
Scope string `json:"scope"`
}
func BuildClaims(user *db.UserSchema) *Claims {
var c = &Claims{
Id: user.Id,
Username: user.Username,
Email: user.Email,
EmailIsVerified: user.EmailIsVerified,
XSRF: uuid.New().String(),
Scope: user.Scope,
Policies: user.Policies,
}
c.IssuedAt = jwt.NewNumericDate(time.Now())
c.NotBefore = jwt.NewNumericDate(time.Now())
c.Issuer = config.JwtIssuer()
c.Audience = config.JwtAudience()
if c.Scope == "" {
c.Scope = "default"
}
return c
}

@ -1,72 +0,0 @@
package token
import (
"log"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/mnrva-dev/owltier.com/server/config"
"github.com/mnrva-dev/owltier.com/server/db"
)
const (
ACCESS_EXPIRATION = 10 * time.Minute
REFRESH_EXPIRATION = 7 * 24 * time.Hour
VERIFY_EMAIL_EXPIRATION = 15 * time.Minute
)
func GenerateAccess(user *db.UserSchema) string {
c := BuildClaims(user)
c.ExpiresAt = jwt.NewNumericDate(time.Now().Add(ACCESS_EXPIRATION))
c.Type = TypeAccess
c.Id = user.Id
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
// Sign and get the complete encoded token as a string using the secret
tokenString, err := token.SignedString(config.AccessSecret())
if err != nil {
log.Println(err)
return ""
}
return tokenString
}
func GenerateRefresh(user *db.UserSchema) string {
c := BuildClaims(user)
c.ExpiresAt = jwt.NewNumericDate(time.Now().Add(REFRESH_EXPIRATION))
c.Type = TypeRefresh
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
// Sign and get the complete encoded token as a string using the secret
tokenString, err := token.SignedString(config.RefreshSecret())
if err != nil {
log.Println(err)
return ""
}
return tokenString
}
func GenerateVerifyEmail(user *db.UserSchema) string {
c := &Claims{}
c.Id = user.Id
c.Email = user.Email
c.IssuedAt = jwt.NewNumericDate(time.Now())
c.NotBefore = jwt.NewNumericDate(time.Now())
c.Issuer = config.JwtIssuer()
c.Audience = config.JwtAudience()
c.ExpiresAt = jwt.NewNumericDate(time.Now().Add(VERIFY_EMAIL_EXPIRATION))
c.Type = TypeVerifyEmail
c.Scope = "verify-email"
token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
// Sign and get the complete encoded token as a string using the secret
tokenString, err := token.SignedString(config.EmailTokenSecret())
if err != nil {
log.Println(err)
return ""
}
return tokenString
}

@ -1,92 +0,0 @@
package token_test
import (
"testing"
"github.com/mnrva-dev/owltier.com/server/db"
"github.com/mnrva-dev/owltier.com/server/token"
)
var ts string
// TODO Write actual unit tests
func TestMain(m *testing.M) {
ts = token.GenerateAccess(&db.UserSchema{
Id: "id_1234",
Username: "myusername",
Email: "user@example.com",
Scope: "admin",
Policies: []string{"policy1", "policy2"},
})
m.Run()
}
func TestAccessIdentity(t *testing.T) {
if ts == "" {
t.Fatal("No token was created")
}
c, err := token.ValidateAccess(ts)
if err != nil {
t.Errorf("Expected valid token, got %v", err)
}
if c.Email != "user@example.com" || c.Id != "id_1234" || c.Username != "myusername" {
t.Errorf("Unexpected identity, got %v", c)
}
}
func TestAccessScope(t *testing.T) {
c, err := token.ValidateAccess(ts)
if err != nil {
t.Errorf("Expected valid token, got %v", err)
}
if c.Scope != "admin" {
t.Errorf("Unexpected scope, expected %v got %v", "admin", c.Scope)
}
otherts := token.GenerateAccess(&db.UserSchema{
Id: "id_1234",
Username: "myusername",
Email: "user@example.com",
Policies: []string{"policy1", "policy2"},
})
c, err = token.ValidateAccess(otherts)
if err != nil {
t.Errorf("Expected valid token, got %v", err)
}
if c.Scope != "default" {
t.Errorf("Unexpected scope, expected %v got %v", "default", c.Scope)
}
}
func TestAccessPolicies(t *testing.T) {
ts := token.GenerateAccess(&db.UserSchema{
Id: "id_1234",
Username: "myusername",
Email: "user@example.com",
Scope: "admin",
Policies: []string{"policy1", "policy2"},
})
if ts == "" {
t.Error("No token was created")
}
c, err := token.ValidateAccess(ts)
if err != nil {
t.Errorf("Expected valid token, got %v", err)
}
if len(c.Policies) != 2 || c.Policies[0] != "policy1" || c.Policies[1] != "policy2" {
t.Errorf("Unexpected policies, got %v", c.Policies)
}
}
func TestRefresh(t *testing.T) {
ts := token.GenerateRefresh(&db.UserSchema{
Id: "12345",
})
if ts == "" {
t.Error("No token was created")
}
c, err := token.ValidateRefresh(ts)
if err != nil {
t.Errorf("Expected valid token, got %v", err)
}
if c.Id != "12345" {
t.Errorf("Unexpected identity, got %v", c)
}
}

@ -1,7 +0,0 @@
package token
const (
TypeRefresh = "Refresh"
TypeAccess = "Access"
TypeVerifyEmail = "VerifyEmail"
)

@ -1,66 +0,0 @@
package token
import (
"fmt"
"github.com/golang-jwt/jwt/v5"
"github.com/mnrva-dev/owltier.com/server/config"
"golang.org/x/exp/slices"
)
func ValidateAccess(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return config.AccessSecret(), nil
})
if claims, ok := token.Claims.(*Claims); ok {
if !token.Valid {
return nil, fmt.Errorf("token is not valid")
}
if !slices.Contains(claims.Audience, "https://gosuimg.com") {
return nil, fmt.Errorf("unexpected audience value: %v", claims.Audience)
}
if claims.Type != "Access" {
return nil, fmt.Errorf("Unexpected token type: %v", claims.Type)
}
return claims, nil
} else {
return nil, err
}
}
func ValidateRefresh(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return config.RefreshSecret(), nil
})
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
if !slices.Contains(claims.Audience, "https://gosuimg.com") {
return &Claims{}, fmt.Errorf("unexpected audience value: %v", claims.Audience)
}
if claims.Type != "Refresh" {
return &Claims{}, fmt.Errorf("Unexpected token type: %v", claims.Type)
}
return claims, nil
} else {
return &Claims{}, err
}
}
func ValidateVerifyEmail(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return config.RefreshSecret(), nil
})
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
if !slices.Contains(claims.Audience, "https://gosuimg.com") {
return &Claims{}, fmt.Errorf("unexpected audience value: %v", claims.Audience)
}
if claims.Type != "VerifyEmail" {
return &Claims{}, fmt.Errorf("Unexpected token type: %v", claims.Type)
}
return claims, nil
} else {
return &Claims{}, err
}
}
Loading…
Cancel
Save