parent
9c06545931
commit
c8697d6ef6
@ -0,0 +1,5 @@
|
||||
package auth
|
||||
|
||||
const (
|
||||
SESSION_COOKIE = "_owltier.com_sess"
|
||||
)
|
||||
@ -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)
|
||||
}
|
||||
@ -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…
Reference in new issue