implement auth
ci.vdhsn.com/push Build is failing
Details
ci.vdhsn.com/push Build is failing
Details
parent
649bcefbef
commit
5a191a2c72
|
|
@ -54,3 +54,4 @@ stringData:
|
|||
port: 80
|
||||
endpoints:
|
||||
catalog: catalog:5001
|
||||
auth: auth:5001
|
||||
|
|
|
|||
|
|
@ -8,3 +8,4 @@ stringData:
|
|||
port: 80
|
||||
endpoints:
|
||||
catalog: catalog-local:5001
|
||||
auth: auth-local:5001
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ CREATE TABLE IF NOT EXISTS auth.account (
|
|||
email VARCHAR(512) NOT NULL,
|
||||
passwordHash VARCHAR(512) NOT NULL,
|
||||
role VARCHAR(64) NOT NULL DEFAULT 'bidder',
|
||||
enabled BIT NOT NULL DEFAULT 1
|
||||
enabled BOOLEAN NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS auth.account_verification (
|
||||
|
|
@ -91,9 +91,6 @@ END;
|
|||
$BODY$;
|
||||
-- +goose StatementEnd
|
||||
|
||||
|
||||
|
||||
|
||||
-- +goose StatementBegin
|
||||
DO
|
||||
$do$
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ SELECT
|
|||
createdTs,
|
||||
verifiedTs,
|
||||
email,
|
||||
role
|
||||
role,
|
||||
enabled
|
||||
FROM auth.account WHERE
|
||||
email = sqlc.arg(email) OR id = sqlc.arg(id);
|
||||
|
||||
|
|
@ -21,3 +22,12 @@ SELECT auth.bh_verify_account(
|
|||
sqlc.arg(token)
|
||||
);
|
||||
|
||||
|
||||
-- name: CreateSession :one
|
||||
INSERT INTO auth.sessions (
|
||||
accountId,
|
||||
createdTs
|
||||
) VALUES (
|
||||
sqlc.arg(accountId),
|
||||
NOW()
|
||||
) RETURNING *;
|
||||
|
|
|
|||
|
|
@ -4,19 +4,22 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/data/postgres"
|
||||
)
|
||||
|
||||
type PGRunnerStorage struct {
|
||||
*postgres.Queries
|
||||
}
|
||||
type (
|
||||
PGRunnerStorage struct {
|
||||
*postgres.Queries
|
||||
}
|
||||
|
||||
type RegisterAccountInput struct {
|
||||
Email string
|
||||
PasswordHash string
|
||||
Role string
|
||||
}
|
||||
RegisterAccountInput struct {
|
||||
Email string
|
||||
PasswordHash string
|
||||
Role string
|
||||
}
|
||||
)
|
||||
|
||||
func (db *PGRunnerStorage) RegisterAccount(ctx context.Context, in RegisterAccountInput) (accountId int, err error) {
|
||||
var accId int32
|
||||
|
|
@ -33,23 +36,70 @@ func (db *PGRunnerStorage) RegisterAccount(ctx context.Context, in RegisterAccou
|
|||
return
|
||||
}
|
||||
|
||||
type GetAccountInput struct {
|
||||
Email string
|
||||
AccountID int
|
||||
}
|
||||
type (
|
||||
GetAccountInput struct {
|
||||
Email string
|
||||
AccountID int
|
||||
}
|
||||
|
||||
AccountResult struct {
|
||||
ID int `json:"id"`
|
||||
Created time.Time `json:"created"`
|
||||
Verified time.Time `json:"verified,omitempty"`
|
||||
Email string `json:"email"`
|
||||
PasswordHash string `json:"password_hash,omitempty"`
|
||||
Role string `json:"role"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
)
|
||||
|
||||
func (db *PGRunnerStorage) GetAccount(ctx context.Context, in GetAccountInput) (out AccountResult, err error) {
|
||||
var row postgres.GetAccountByEmailOrIDRow
|
||||
if row, err = db.Queries.GetAccountByEmailOrID(ctx, postgres.GetAccountByEmailOrIDParams{
|
||||
Email: in.Email,
|
||||
ID: int32(in.AccountID),
|
||||
}); err != nil {
|
||||
err = fmt.Errorf("could not get account: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
out = AccountResult{
|
||||
ID: int(row.ID),
|
||||
Email: row.Email,
|
||||
Role: row.Role,
|
||||
Created: row.Createdts,
|
||||
Verified: row.Verifiedts.Time,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
func (db *PGRunnerStorage) GetAccount(ctx context.Context, in GetAccountInput) (err error) {
|
||||
err = errors.New("Unimplemented")
|
||||
return
|
||||
}
|
||||
|
||||
type CreateSessionInput struct {
|
||||
Email string
|
||||
AccountID int
|
||||
}
|
||||
type (
|
||||
CreateSessionInput struct {
|
||||
Email string
|
||||
AccountID int
|
||||
}
|
||||
|
||||
Session struct {
|
||||
ID int
|
||||
AccountID int
|
||||
Created time.Time
|
||||
}
|
||||
)
|
||||
|
||||
func (db *PGRunnerStorage) CreateSession(ctx context.Context, in CreateSessionInput) (out Session, err error) {
|
||||
var session postgres.AuthSession
|
||||
if session, err = db.Queries.CreateSession(ctx, int32(in.AccountID)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out = Session{
|
||||
ID: int(session.ID),
|
||||
AccountID: int(session.Accountid),
|
||||
Created: session.Createdts,
|
||||
}
|
||||
|
||||
func (db *PGRunnerStorage) CreateSession(ctx context.Context, in CreateSessionInput) (err error) {
|
||||
err = errors.New("Unimplemented")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,161 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/data"
|
||||
)
|
||||
|
||||
type (
|
||||
Domain struct {
|
||||
Storage
|
||||
PasswordHasher
|
||||
}
|
||||
|
||||
Storage interface{}
|
||||
HashedPassword []byte
|
||||
|
||||
PasswordHasher interface {
|
||||
Hash(string) HashedPassword
|
||||
Compare(string, HashedPassword) bool
|
||||
}
|
||||
|
||||
Storage interface {
|
||||
RegisterAccount(context.Context, data.RegisterAccountInput) (int, error)
|
||||
GetAccount(context.Context, data.GetAccountInput) (data.AccountResult, error)
|
||||
CreateSession(context.Context, data.CreateSessionInput) (data.Session, error)
|
||||
VerifyAccount(context.Context, data.VerifyAccountInput) error
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
CreateAccountCommand struct {
|
||||
Email string
|
||||
Password string
|
||||
Role Role
|
||||
}
|
||||
AccountCreated struct {
|
||||
Account
|
||||
}
|
||||
)
|
||||
|
||||
func (d *Domain) CreateAccount(ctx context.Context, in CreateAccountCommand) (out AccountCreated, err error) {
|
||||
if in.Email == "" {
|
||||
err = errors.New("email cannot be empty")
|
||||
return
|
||||
}
|
||||
|
||||
if in.Password == "" {
|
||||
err = errors.New("password cannot be empty")
|
||||
return
|
||||
}
|
||||
|
||||
if in.Role == EmptyRole {
|
||||
in.Role = UserRole
|
||||
}
|
||||
|
||||
var accountID int
|
||||
if accountID, err = d.Storage.RegisterAccount(ctx, data.RegisterAccountInput{
|
||||
Email: in.Email,
|
||||
PasswordHash: in.Password,
|
||||
Role: in.Role.String(),
|
||||
}); err != nil {
|
||||
err = fmt.Errorf("could not register account: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
var ar data.AccountResult
|
||||
if ar, err = d.Storage.GetAccount(ctx, data.GetAccountInput{
|
||||
Email: in.Email,
|
||||
AccountID: accountID,
|
||||
}); err != nil {
|
||||
err = fmt.Errorf("could not find account by ID: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
out = AccountCreated{
|
||||
Account: Account{
|
||||
ID: ar.ID,
|
||||
Created: ar.Created,
|
||||
Verified: ar.Verified,
|
||||
Email: ar.Email,
|
||||
Role: Role(ar.Role),
|
||||
Enabled: ar.Enabled,
|
||||
},
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type (
|
||||
LoginCommand struct{}
|
||||
LoggedIn struct {
|
||||
Account
|
||||
SessionID int
|
||||
Created time.Time
|
||||
}
|
||||
)
|
||||
|
||||
func (d *Domain) Login(ctx context.Context, in LoginCommand) (out LoggedIn, err error) {
|
||||
var sess data.Session
|
||||
if sess, err = d.Storage.CreateSession(ctx, data.CreateSessionInput{}); err != nil {
|
||||
err = fmt.Errorf("could not create session: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
var ar data.AccountResult
|
||||
if ar, err = d.Storage.GetAccount(ctx, data.GetAccountInput{
|
||||
AccountID: sess.AccountID,
|
||||
}); err != nil {
|
||||
err = fmt.Errorf("could not find account by ID: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
out = LoggedIn{
|
||||
Account: Account{
|
||||
ID: ar.ID,
|
||||
Created: ar.Created,
|
||||
Verified: ar.Verified,
|
||||
Email: ar.Email,
|
||||
Role: Role(ar.Role),
|
||||
Enabled: ar.Enabled,
|
||||
},
|
||||
Created: sess.Created,
|
||||
SessionID: sess.ID,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type (
|
||||
GetAccountCommand struct{}
|
||||
)
|
||||
|
||||
func (d *Domain) GetAccount(ctx context.Context, in GetAccountCommand) (out Account, err error) {
|
||||
var ar data.AccountResult
|
||||
if ar, err = d.Storage.GetAccount(ctx, data.GetAccountInput{}); err != nil {
|
||||
err = fmt.Errorf("could not get account: %w", err)
|
||||
}
|
||||
|
||||
out = Account{
|
||||
ID: ar.ID,
|
||||
Created: ar.Created,
|
||||
Verified: ar.Verified,
|
||||
Email: ar.Email,
|
||||
Role: Role(ar.Role),
|
||||
Enabled: ar.Enabled,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type (
|
||||
VerifyAccountCommand struct{}
|
||||
AccountVerified struct{}
|
||||
)
|
||||
|
||||
func (d *Domain) VerifyAccount(ctx context.Context, in VerifyAccountCommand) (out AccountVerified, err error) {
|
||||
err = errors.New("Unimplemented")
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestDomain_CreateAccount(t *testing.T) {
|
||||
type fields struct {
|
||||
Storage Storage
|
||||
}
|
||||
type args struct {
|
||||
in CreateAccountCommand
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantOut AccountCreated
|
||||
wantErr bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
d := &Domain{
|
||||
Storage: tt.fields.Storage,
|
||||
}
|
||||
gotOut, err := d.CreateAccount(tt.args.in)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Domain.CreateAccount() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(gotOut, tt.wantOut) {
|
||||
t.Errorf("Domain.CreateAccount() = %v, want %v", gotOut, tt.wantOut)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -2,13 +2,26 @@ package domain
|
|||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
UserRole = Role("USER")
|
||||
AdminRole = Role("ADMINISRATOR")
|
||||
AnonymousRole = Role("ANONYMOUS")
|
||||
EmptyRole = Role("")
|
||||
)
|
||||
|
||||
type Role string
|
||||
|
||||
func (r Role) String() string {
|
||||
return string(r)
|
||||
}
|
||||
|
||||
type Account struct {
|
||||
ID int `json:"id"`
|
||||
Created time.Time `json:"created"`
|
||||
Verified time.Time `json:"verified,omitempty"`
|
||||
Email string `json:"email"`
|
||||
PasswordHash string `json:"password_hash,omitempty"`
|
||||
Role string `json:"role"`
|
||||
Role Role `json:"role"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
api "git.vdhsn.com/barretthousen/barretthousen/src/auth/api/grpc"
|
||||
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/domain"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func NewAuthServer(d *domain.Domain) func(grpcServer grpc.ServiceRegistrar, endpoint string) {
|
||||
|
|
@ -16,3 +21,34 @@ type authHandler struct {
|
|||
api.UnimplementedAuthServer
|
||||
domain *domain.Domain
|
||||
}
|
||||
|
||||
func (a *authHandler) CreateAccount(ctx context.Context, in *api.CreateAccountInput) (*api.Account, error) {
|
||||
account, err := a.domain.CreateAccount(ctx, domain.CreateAccountCommand{
|
||||
Email: in.Email,
|
||||
Password: in.Password,
|
||||
Role: domain.Role(in.Role),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &api.Account{
|
||||
Id: int32(account.ID),
|
||||
Email: account.Email,
|
||||
Role: account.Role.String(),
|
||||
CreatedTs: timestamppb.New(account.Created),
|
||||
VerifiedTs: timestamppb.New(account.Verified),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *authHandler) Login(ctx context.Context, in *api.LoginInput) (*api.LoginResult, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Login not implemented")
|
||||
}
|
||||
|
||||
func (a *authHandler) VerifyAccount(ctx context.Context, in *api.VerificationParameters) (*api.AccountVerifiedResult, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method VerifyAccount not implemented")
|
||||
}
|
||||
|
||||
func (a *authHandler) GetAccount(ctx context.Context, in *api.AccountFilter) (*api.Account, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetAccount not implemented")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
auth_api "git.vdhsn.com/barretthousen/barretthousen/src/auth/api/grpc"
|
||||
api "git.vdhsn.com/barretthousen/barretthousen/src/catalog/api/grpc"
|
||||
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
|
|
@ -19,13 +20,14 @@ type ProxyClientApp struct {
|
|||
LogLevel kernel.LogLevel `yaml:"log_level" yaml-default:"0"`
|
||||
Port int `yaml:"port"`
|
||||
Endpoints struct {
|
||||
Catalog string `yaml:"catalog" env:"CATALOG_ENDPOINT"`
|
||||
Catalog string `yaml:"catalog"`
|
||||
Auth string `yaml:"auth"`
|
||||
} `yaml:"endpoints" env:"PROXY_CLIENT_SERVICES"`
|
||||
}
|
||||
|
||||
func (app *ProxyClientApp) Start(ctx context.Context) error {
|
||||
grpcMux := runtime.NewServeMux()
|
||||
err := api.RegisterCatalogHandlerFromEndpoint(ctx, grpcMux, app.Endpoints.Catalog, []grpc.DialOption{
|
||||
grpcOpts := []grpc.DialOption{
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithConnectParams(grpc.ConnectParams{
|
||||
Backoff: backoff.Config{
|
||||
|
|
@ -33,8 +35,13 @@ func (app *ProxyClientApp) Start(ctx context.Context) error {
|
|||
},
|
||||
MinConnectTimeout: time.Second,
|
||||
}),
|
||||
})
|
||||
if err != nil {
|
||||
}
|
||||
|
||||
if err := api.RegisterCatalogHandlerFromEndpoint(ctx, grpcMux, app.Endpoints.Catalog, grpcOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := auth_api.RegisterAuthHandlerFromEndpoint(ctx, grpcMux, app.Endpoints.Auth, grpcOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts">
|
||||
let showPassword = false;
|
||||
let showRegistration = false;
|
||||
|
||||
const revealPass = () => (showPassword = !showPassword);
|
||||
|
||||
|
|
@ -18,6 +19,7 @@
|
|||
<span class="flex">
|
||||
<input class="py-1" type="text" />
|
||||
<button class="w-20"> Login </button>
|
||||
<button class="w-20"> Sign Up </button>
|
||||
</span>
|
||||
<span class="flex">
|
||||
<input
|
||||
|
|
@ -26,6 +28,14 @@
|
|||
on:blur={() => (showPassword = false)}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
/>
|
||||
{#if showRegistration}
|
||||
<input
|
||||
class="px-2 py-1 border-r-0"
|
||||
name="confirm_password"
|
||||
on:blur={() => (showPassword = false)}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
/>
|
||||
{/if}
|
||||
<button class="border-l-0 px-2 py-1 w-16" on:click|stopPropagation={revealPass}>
|
||||
{showPassword ? 'Hide' : 'Show'}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
<script lang="ts">
|
||||
let showPassword = false;
|
||||
let showRegistration = false;
|
||||
|
||||
const revealPass = () => (showPassword = !showPassword);
|
||||
|
||||
function execLogin() {
|
||||
console.log('LOGGING IN');
|
||||
}
|
||||
</script>
|
||||
|
||||
<form class="flex" on:submit|preventDefault={execLogin}>
|
||||
<span class="flex flex-col justify-around w-20">
|
||||
<label for="email"> Email </label>
|
||||
<label for="password"> Password </label>
|
||||
</span>
|
||||
|
||||
<span class="flex flex-col">
|
||||
<span class="flex">
|
||||
<input class="py-1" type="text" />
|
||||
<button class="w-20"> Login </button>
|
||||
</span>
|
||||
<span class="flex">
|
||||
<input
|
||||
class="px-2 py-1 border-r-0"
|
||||
name="password"
|
||||
on:blur={() => (showPassword = false)}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
/>
|
||||
{#if showRegistration}
|
||||
<input
|
||||
class="px-2 py-1 border-r-0"
|
||||
name="confirm_password"
|
||||
on:blur={() => (showPassword = false)}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
/>
|
||||
{/if}
|
||||
<button class="border-l-0 px-2 py-1 w-16" on:click|stopPropagation={revealPass}>
|
||||
{showPassword ? 'Hide' : 'Show'}
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</form>
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<script lang="ts">
|
||||
let password;
|
||||
let confirm_password;
|
||||
let email;
|
||||
|
||||
let showPassword = false;
|
||||
|
||||
const revealPass = () => (showPassword = !showPassword);
|
||||
|
||||
function execRegister() {
|
||||
console.log('Regstering User');
|
||||
}
|
||||
</script>
|
||||
|
||||
<form class="flex" on:submit|preventDefault={execRegister}>
|
||||
<span class="flex flex-col justify-around w-20">
|
||||
<label for="email"> Email </label>
|
||||
<label for="password"> Password </label>
|
||||
</span>
|
||||
|
||||
<span class="flex flex-col">
|
||||
<span class="flex">
|
||||
<input class="py-1" type="text" />
|
||||
<button class="w-20"> Login </button>
|
||||
</span>
|
||||
<span class="flex">
|
||||
<input
|
||||
class="px-2 py-1 border-r-0"
|
||||
name="password"
|
||||
on:blur={() => (showPassword = false)}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
/>
|
||||
<input
|
||||
class="px-2 py-1 border-r-0"
|
||||
name="confirm_password"
|
||||
on:blur={() => (showPassword = false)}
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
/>
|
||||
<button class="border-l-0 px-2 py-1 w-16" on:click|stopPropagation={revealPass}>
|
||||
{showPassword ? 'Hide' : 'Show'}
|
||||
</button>
|
||||
</span>
|
||||
</span>
|
||||
</form>
|
||||
Loading…
Reference in New Issue