Add dynamic admin panel for managing entities (#108)
This commit is contained in:
parent
60009df0bf
commit
1a6874fd82
47 changed files with 2173 additions and 320 deletions
|
|
@ -106,15 +106,6 @@ func (c *AuthClient) GetAuthenticatedUser(ctx echo.Context) (*ent.User, error) {
|
|||
return nil, NotAuthenticatedError{}
|
||||
}
|
||||
|
||||
// HashPassword returns a hash of a given password
|
||||
func (c *AuthClient) HashPassword(password string) (string, error) {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(hash), nil
|
||||
}
|
||||
|
||||
// CheckPassword check if a given password matches a given hash
|
||||
func (c *AuthClient) CheckPassword(password, hash string) error {
|
||||
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
|
||||
|
|
@ -123,7 +114,7 @@ func (c *AuthClient) CheckPassword(password, hash string) error {
|
|||
// GeneratePasswordResetToken generates a password reset token for a given user.
|
||||
// For security purposes, the token itself is not stored in the database but rather
|
||||
// a hash of the token, exactly how passwords are handled. This method returns both
|
||||
// the generated token as well as the token entity which only contains the hash.
|
||||
// the generated token and the token entity which only contains the hash.
|
||||
func (c *AuthClient) GeneratePasswordResetToken(ctx echo.Context, userID int) (string, *ent.PasswordToken, error) {
|
||||
// Generate the token, which is what will go in the URL, but not the database
|
||||
token, err := c.RandomToken(c.config.App.PasswordToken.Length)
|
||||
|
|
@ -131,16 +122,10 @@ func (c *AuthClient) GeneratePasswordResetToken(ctx echo.Context, userID int) (s
|
|||
return "", nil, err
|
||||
}
|
||||
|
||||
// Hash the token, which is what will be stored in the database
|
||||
hash, err := c.HashPassword(token)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
// Create and save the password reset token
|
||||
pt, err := c.orm.PasswordToken.
|
||||
Create().
|
||||
SetHash(hash).
|
||||
SetToken(token).
|
||||
SetUserID(userID).
|
||||
Save(ctx.Request().Context())
|
||||
|
||||
|
|
@ -166,7 +151,7 @@ func (c *AuthClient) GetValidPasswordToken(ctx echo.Context, userID, tokenID int
|
|||
case *ent.NotFoundError:
|
||||
case nil:
|
||||
// Check the token for a hash match
|
||||
if err := c.CheckPassword(token, pt.Hash); err == nil {
|
||||
if err := c.CheckPassword(token, pt.Token); err == nil {
|
||||
return pt, nil
|
||||
}
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/mikestefanello/pagoda/ent/passwordtoken"
|
||||
"github.com/mikestefanello/pagoda/ent/user"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
|
@ -41,12 +42,12 @@ func TestAuthClient_Auth(t *testing.T) {
|
|||
assertNoAuth()
|
||||
}
|
||||
|
||||
func TestAuthClient_PasswordHashing(t *testing.T) {
|
||||
func TestAuthClient_CheckPassword(t *testing.T) {
|
||||
pw := "testcheckpassword"
|
||||
hash, err := c.Auth.HashPassword(pw)
|
||||
assert.NoError(t, err)
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)
|
||||
require.NoError(t, err)
|
||||
assert.NotEqual(t, hash, pw)
|
||||
err = c.Auth.CheckPassword(pw, hash)
|
||||
err = c.Auth.CheckPassword(pw, string(hash))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
|
|
@ -54,7 +55,7 @@ func TestAuthClient_GeneratePasswordResetToken(t *testing.T) {
|
|||
token, pt, err := c.Auth.GeneratePasswordResetToken(ctx, usr.ID)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, token, c.Config.App.PasswordToken.Length)
|
||||
assert.NoError(t, c.Auth.CheckPassword(token, pt.Hash))
|
||||
assert.NoError(t, c.Auth.CheckPassword(token, pt.Token))
|
||||
}
|
||||
|
||||
func TestAuthClient_GetValidPasswordToken(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -6,17 +6,21 @@ import (
|
|||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/mikestefanello/backlite"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
entsql "entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/entc"
|
||||
"entgo.io/ent/entc/gen"
|
||||
"github.com/labstack/echo/v4"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/mikestefanello/backlite"
|
||||
"github.com/mikestefanello/pagoda/config"
|
||||
"github.com/mikestefanello/pagoda/ent"
|
||||
"github.com/mikestefanello/pagoda/pkg/log"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
// Required by ent.
|
||||
_ "github.com/mikestefanello/pagoda/ent/runtime"
|
||||
|
|
@ -46,6 +50,9 @@ type Container struct {
|
|||
// ORM stores a client to the ORM.
|
||||
ORM *ent.Client
|
||||
|
||||
// Graph is the entity graph defined by your Ent schema.
|
||||
Graph *gen.Graph
|
||||
|
||||
// Mail stores an email sending client.
|
||||
Mail *MailClient
|
||||
|
||||
|
|
@ -184,6 +191,16 @@ func (c *Container) initORM() {
|
|||
if err := c.ORM.Schema.Create(context.Background()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Load the graph.
|
||||
_, b, _, _ := runtime.Caller(0)
|
||||
d := path.Join(path.Dir(b))
|
||||
p := filepath.Join(filepath.Dir(d), "../ent/schema")
|
||||
g, err := entc.LoadGraph(p, &gen.Config{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.Graph = g
|
||||
}
|
||||
|
||||
// initAuth initializes the authentication client.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue