implement auth
ci.vdhsn.com/push Build is failing Details

feat/auth
Adam Veldhousen 11 months ago
parent 649bcefbef
commit 5a191a2c72
Signed by: adam
GPG Key ID: 6DB29003C6DD1E4B

@ -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
}
HashedPassword []byte
PasswordHasher interface {
Hash(string) HashedPassword
Compare(string, HashedPassword) bool
}
Storage interface{}
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&nbsp;</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&nbsp;</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…
Cancel
Save