Squashed commit of the following:
ci.vdhsn.com/push Build is failing
Details
ci.vdhsn.com/push Build is failing
Details
commitfeat/swaggere891ada9e8
Author: Adam Veldhousen <adamveld12@gmail.com> Date: Fri Jul 7 20:10:40 2023 -0500 added login and checking of roles to admin page commitabb8e565cd
Author: Adam Veldhousen <adamveld12@gmail.com> Date: Fri Jul 7 18:23:24 2023 -0500 analytics + login ux updates commit4ab08d20b8
Author: Adam Veldhousen <adamveld12@gmail.com> Date: Sat Jul 1 03:07:36 2023 -0500 grpc reflection commitfe329d2336
Author: Adam Veldhousen <adamveld12@gmail.com> Date: Sat Jul 1 03:07:22 2023 -0500 tests for auth commit043f387224
Author: Adam Veldhousen <adamveld12@gmail.com> Date: Tue Jun 20 09:43:03 2023 -0500 first pass at login/logout and sign up form commit5a191a2c72
Author: Adam Veldhousen <adamveld12@gmail.com> Date: Mon Jun 19 10:30:24 2023 -0500 implement auth commit649bcefbef
Author: Adam Veldhousen <adamveld12@gmail.com> Date: Sun Jun 18 18:29:20 2023 -0500 added login control to page commit4227fc048a
Author: Adam Veldhousen <adamveld12@gmail.com> Date: Wed Jun 14 19:10:54 2023 -0500 early pass at auth service
parent
898ec6ec3d
commit
ad7d811c35
@ -0,0 +1,71 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: auth
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
service: auth
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
service: auth
|
||||
spec:
|
||||
serviceAccountName: barretthousen-service
|
||||
containers:
|
||||
- name: auth
|
||||
image: barretthousen/service-auth:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 5001
|
||||
name: grpc
|
||||
command:
|
||||
- /opt/auth
|
||||
args:
|
||||
- -migrate
|
||||
resources:
|
||||
limits:
|
||||
cpu: "250m"
|
||||
memory: "128Mi"
|
||||
volumeMounts:
|
||||
- mountPath: /config/
|
||||
name: auth-config
|
||||
volumes:
|
||||
- name: auth-config
|
||||
secret:
|
||||
secretName: auth-config
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: auth
|
||||
spec:
|
||||
selector:
|
||||
service: auth
|
||||
ports:
|
||||
- port: 5001
|
||||
targetPort: 5001
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: auth-config
|
||||
stringData:
|
||||
config.yaml: |
|
||||
log_level: 2
|
||||
port: 5001
|
||||
db_service:
|
||||
scheme: postgres
|
||||
port: 5432
|
||||
host: bh-db
|
||||
name: bh
|
||||
user: auth-service
|
||||
password: auth-service
|
||||
db_migrate:
|
||||
scheme: postgres
|
||||
port: 5432
|
||||
host: bh-db
|
||||
name: bh
|
||||
user: postgres
|
||||
password: bh-admin
|
@ -0,0 +1,11 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: auth
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: auth
|
||||
ports:
|
||||
- containerPort: 2345
|
@ -1,9 +1,10 @@
|
||||
go 1.19
|
||||
|
||||
use (
|
||||
./src/catalog
|
||||
./src/lib
|
||||
./src/auth
|
||||
./src/catalog
|
||||
./src/runner
|
||||
./src/proxy-admin
|
||||
./src/proxy-web
|
||||
./src/runner
|
||||
)
|
||||
|
@ -0,0 +1,95 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
interface FormValidations {
|
||||
email?: boolean;
|
||||
general?: boolean;
|
||||
}
|
||||
|
||||
export const validations: FormValidations = {};
|
||||
|
||||
let email: string = '';
|
||||
let password: string = '';
|
||||
let passwordConfirmation: string = '';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const execLogin = () =>
|
||||
dispatch('login', {
|
||||
email,
|
||||
password
|
||||
});
|
||||
|
||||
const execRegister = () => {
|
||||
if (password !== passwordConfirmation) {
|
||||
console.log("password and confirm don't match");
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch('register', {
|
||||
email,
|
||||
password
|
||||
});
|
||||
};
|
||||
|
||||
let showPassword = false;
|
||||
let showRegistration = false;
|
||||
|
||||
const revealPass = () => (showPassword = !showPassword);
|
||||
const toggleFormType = () => (showRegistration = !showRegistration);
|
||||
|
||||
$: ctaTxt = showRegistration ? 'Up' : 'In';
|
||||
$: toggleCtaTxt = !showRegistration ? 'Up' : 'In';
|
||||
$: ctaFunc = showRegistration ? execRegister : execLogin;
|
||||
</script>
|
||||
|
||||
<form class="flex" style="max-width: 600px;" on:submit|preventDefault={ctaFunc}>
|
||||
<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="px-2 py-1 border-r-0 grow invalid:border-red-500 invalid:border-2"
|
||||
type="email"
|
||||
required
|
||||
bind:value={email}
|
||||
/>
|
||||
<button class="border-l-0 px-2 py-1 disabled:text-gray-500">
|
||||
Sign {ctaTxt}
|
||||
</button>
|
||||
</span>
|
||||
<span class="flex">
|
||||
<input
|
||||
class="px-2 py-1 w-full"
|
||||
name="password"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
required
|
||||
placeholder={showRegistration ? 'Password' : ''}
|
||||
on:blur={() => (showPassword = false)}
|
||||
on:change={(evt) => (password = evt?.target?.value)}
|
||||
/>
|
||||
{#if showRegistration}
|
||||
<input
|
||||
class="px-2 py-1 border-r-0 invalid:border-red-500 invalid:border-2"
|
||||
name="confirm_password"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
pattern={password}
|
||||
required
|
||||
placeholder="Confirm Password"
|
||||
on:blur={() => (showPassword = false)}
|
||||
on:change={(evt) => (passwordConfirmation = evt?.target?.value)}
|
||||
/>
|
||||
|
||||
<button
|
||||
class="border-l-0 px-2 py-1 w-16"
|
||||
tabindex="-1"
|
||||
on:click|stopPropagation={revealPass}
|
||||
>
|
||||
{showPassword ? 'Hide' : 'Show'}
|
||||
</button>
|
||||
{/if}
|
||||
</span>
|
||||
</span>
|
||||
</form>
|
@ -0,0 +1,119 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { env } from '$env/dynamic/public';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
const API_HOST = `${browser ? '' : env.BH_CLIENT_INTERNAL_API_HOST}/api/v1`;
|
||||
|
||||
interface SessionInfo {
|
||||
sessionId?: string;
|
||||
account?: {
|
||||
id: string;
|
||||
email: string;
|
||||
role: 'BIDDER' | 'USER' | 'ADMINISTRATOR' | 'ANONYMOUS';
|
||||
createdTs: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const getSession = () => {
|
||||
if (browser) {
|
||||
const strSession = localStorage.getItem('bh-session');
|
||||
if (strSession) {
|
||||
const data = JSON.parse(strSession);
|
||||
if (data?.sessionId) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
export const session = writable<SessionInfo>(getSession(), (set) => {
|
||||
const session = getSession();
|
||||
if (session) {
|
||||
set(session);
|
||||
}
|
||||
});
|
||||
|
||||
interface LoginAction {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export const loginAction = async ({ email, password }: LoginAction): Promise<SessionInfo> => {
|
||||
try {
|
||||
const sessionStr = localStorage.getItem('bh-session');
|
||||
if (sessionStr) {
|
||||
const data = JSON.parse(sessionStr);
|
||||
if (data.sessionId) {
|
||||
console.log('already authenticated');
|
||||
session.set(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
localStorage.removeItem('bh-session');
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
new Request(`${API_HOST}/user`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
|
||||
if (data.sessionId) {
|
||||
localStorage.setItem('bh-session', JSON.stringify(data));
|
||||
return data;
|
||||
}
|
||||
|
||||
console.trace("got this on login attempt:", data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
interface RegisterAction {
|
||||
email: string;
|
||||
password: string;
|
||||
role?: string;
|
||||
}
|
||||
export const registerAction = async ({ email, password, role }: RegisterAction) => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
new Request(`${API_HOST}/user`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password,
|
||||
role: role || 'USER'
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
export const logoutAction = () => {
|
||||
localStorage.removeItem('bh-session');
|
||||
session.set({});
|
||||
};
|
@ -0,0 +1,25 @@
|
||||
tests:
|
||||
- description: "Register User"
|
||||
request:
|
||||
path: "/v1/user"
|
||||
method: "PUT"
|
||||
response:
|
||||
statusCodes: [200]
|
||||
- description: "Login"
|
||||
request:
|
||||
path: "/v1/user"
|
||||
method: "POST"
|
||||
response:
|
||||
statusCodes: [200]
|
||||
- description: "Account Details"
|
||||
request:
|
||||
path: "/v1/user"
|
||||
method: "GET"
|
||||
response:
|
||||
statusCodes: [200]
|
||||
- description: "Verify Account"
|
||||
request:
|
||||
path: "/v1/user/verify"
|
||||
method: "GET"
|
||||
response:
|
||||
statusCodes: [200]
|
@ -0,0 +1,64 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
aapi "git.vdhsn.com/barretthousen/barretthousen/src/auth/api/grpc"
|
||||
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/domain"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func NewAuthServiceClient(conn grpc.ClientConnInterface) *AuthServiceClient {
|
||||
return &AuthServiceClient{
|
||||
AuthClient: aapi.NewAuthClient(conn),
|
||||
}
|
||||
}
|
||||
|
||||
type AuthServiceClient struct {
|
||||
aapi.AuthClient
|
||||
}
|
||||
|
||||
type CheckSessionParams struct {
|
||||
SessionID string
|
||||
}
|
||||
|
||||
func (asc *AuthServiceClient) CheckSession(ctx context.Context, in CheckSessionParams) (out Account, err error) {
|
||||
if in.SessionID == "" {
|
||||
err = domain.ErrInvalidSession
|
||||
return
|
||||
}
|
||||
var sessionIDInt int
|
||||
|
||||
if sessionIDInt, err = strconv.Atoi(in.SessionID); err != nil {
|
||||
return
|
||||
}
|
||||
var accountResult *aapi.Account
|
||||
|
||||
if accountResult, err = asc.GetSession(ctx, &aapi.SessionFilter{
|
||||
SessionId: int32(sessionIDInt),
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
out = Account{
|
||||
ID: int(accountResult.Id),
|
||||
Created: accountResult.CreatedTs.AsTime(),
|
||||
Email: accountResult.Email,
|
||||
Verified: accountResult.VerifiedTs.AsTime(),
|
||||
Role: accountResult.Role,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
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"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package main;
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
import "google/api/annotations.proto";
|
||||
|
||||
option go_package = "git.vdhsn.com/barretthousen/barretthousen/src/auth/api/grpc";
|
||||
|
||||
service Auth {
|
||||
rpc CreateAccount(CreateAccountInput) returns (Account) {
|
||||
option (google.api.http) = {
|
||||
put: "/v1/user"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
rpc Login(LoginInput) returns (LoginResult) {
|
||||
option (google.api.http) = {
|
||||
post: "/v1/user"
|
||||
body: "*"
|
||||
};
|
||||
}
|
||||
|
||||
rpc VerifyAccount(VerificationParameters) returns (AccountVerifiedResult) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/user/verify"
|
||||
};
|
||||
}
|
||||
|
||||
rpc GetAccount(AccountFilter) returns (Account) {
|
||||
option (google.api.http) = {
|
||||
get: "/v1/user"
|
||||
};
|
||||
}
|
||||
|
||||
rpc GetSession(SessionFilter) returns (Account) { }
|
||||
}
|
||||
|
||||
message SessionFilter {
|
||||
int32 sessionId = 1;
|
||||
}
|
||||
|
||||
message CreateAccountInput {
|
||||
string email = 1;
|
||||
string password = 2;
|
||||
string role = 3;
|
||||
}
|
||||
|
||||
message LoginInput {
|
||||
string email = 1;
|
||||
string password = 2;
|
||||
}
|
||||
|
||||
message LoginResult {
|
||||
string sessionId = 1;
|
||||
Account account = 2;
|
||||
}
|
||||
|
||||
message AccountFilter {
|
||||
int32 id = 1;
|
||||
string email = 2;
|
||||
}
|
||||
|
||||
message VerificationParameters {
|
||||
string token = 1;
|
||||
string email = 2;
|
||||
}
|
||||
|
||||
message AccountVerifiedResult {}
|
||||
|
||||
message Account {
|
||||
int32 id = 1;
|
||||
string email = 2;
|
||||
string role = 3;
|
||||
google.protobuf.Timestamp createdTs = 4;
|
||||
google.protobuf.Timestamp verifiedTs = 5;
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
module git.vdhsn.com/barretthousen/barretthousen/src/auth
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
git.vdhsn.com/barretthousen/barretthousen/src/lib v1.0.0
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2
|
||||
go.uber.org/dig v1.16.1
|
||||
golang.org/x/sync v0.2.0
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1
|
||||
google.golang.org/protobuf v1.30.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.2.1 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/ilyakaznacheev/cleanenv v1.4.2 // indirect
|
||||
github.com/jackc/puddle v1.3.0 // indirect
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/pressly/goose/v3 v3.11.0 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
|
||||
github.com/jackc/pgconn v1.14.0
|
||||
github.com/jackc/pgio v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgproto3/v2 v2.3.2 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgtype v1.14.0 // indirect
|
||||
github.com/jackc/pgx/v4 v4.18.1
|
||||
go.uber.org/automaxprocs v1.5.2 // indirect
|
||||
golang.org/x/crypto v0.8.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/grpc v1.55.0
|
||||
)
|
||||
|
||||
replace git.vdhsn.com/barretthousen/barretthousen/src/lib v1.0.0 => ../lib
|
@ -0,0 +1,262 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
|
||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 h1:gDLXvp5S9izjldquuoAhDzccbskOL6tDC5jMSyx3zxE=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2/go.mod h1:7pdNwVWBBHGiCxa9lAszqCJMbfTISJ7oMftp8+UGV08=
|
||||
github.com/ilyakaznacheev/cleanenv v1.4.2 h1:nRqiriLMAC7tz7GzjzUTBHfzdzw6SQ7XvTagkFqe/zU=
|
||||
github.com/ilyakaznacheev/cleanenv v1.4.2/go.mod h1:i0owW+HDxeGKE0/JPREJOdSCPIyOnmh6C0xhWAkF/xA=
|
||||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
|
||||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
|
||||
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
|
||||
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
|
||||
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
|
||||
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
|
||||
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
|
||||
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||
github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q=
|
||||
github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E=
|
||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
||||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
|
||||
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
|
||||
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
|
||||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
|
||||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0=
|
||||
github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
|
||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
||||
github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
|
||||
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
||||
github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0=
|
||||
github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE=
|
||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/jackc/puddle v1.3.0 h1:eHK/5clGOatcjX3oWGBO/MpxpbHzSwud5EWTSCI+MX0=
|
||||
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/pressly/goose/v3 v3.11.0 h1:krazmHhfT6SxJGqtTjddwTsL2Xwje2piTQYRH8KLECI=
|
||||
github.com/pressly/goose/v3 v3.11.0/go.mod h1:ofR04pV2CYY1q/y7CNjoFQuzW4lGSVKwMI/m9lnAjVo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/automaxprocs v1.5.2 h1:2LxUOGiR3O6tw8ui5sZa2LAaHnsviZdVOUZw4fvbnME=
|
||||
go.uber.org/automaxprocs v1.5.2/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
|
||||
go.uber.org/dig v1.16.1 h1:+alNIBsl0qfY0j6epRubp/9obgtrObRAc5aD+6jbWY8=
|
||||
go.uber.org/dig v1.16.1/go.mod h1:557JTAUZT5bUK0SvCwikmLPPtdQhfvLYtO5tJgQSbnk=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
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/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A=
|
||||
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU=
|
||||
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
|
||||
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
|
||||
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo=
|
||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||
modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY=
|
||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/sqlite v1.21.1 h1:GyDFqNnESLOhwwDRaHGdp2jKLDzpyT/rNLglX3ZkMSU=
|
||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ=
|
||||
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw=
|
@ -0,0 +1,111 @@
|
||||
-- +goose Up
|
||||
START TRANSACTION;
|
||||
|
||||
CREATE SCHEMA IF NOT EXISTS auth;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS auth.account (
|
||||
id SERIAL PRIMARY KEY,
|
||||
createdTs TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
verifiedTs TIMESTAMP NULL,
|
||||
email VARCHAR(512) NOT NULL,
|
||||
passwordHash VARCHAR(512) NOT NULL,
|
||||
role VARCHAR(64) NOT NULL DEFAULT 'BIDDER',
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS auth.account_verification (
|
||||
id SERIAL PRIMARY KEY,
|
||||
accountId INT NOT NULL,
|
||||
createdTs TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
verificationToken VARCHAR(128) NOT NULL UNIQUE,
|
||||
CONSTRAINT fk_account_id FOREIGN KEY (accountId) REFERENCES auth.account(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS auth.sessions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
accountId INT NOT NULL,
|
||||
createdTs TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT fk_account_id FOREIGN KEY (accountId) REFERENCES auth.account(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- +goose StatementBegin
|
||||
CREATE OR REPLACE FUNCTION auth.bh_register_account(
|
||||
p_email VARCHAR(512),
|
||||
p_passwordHash VARCHAR(512),
|
||||
p_role VARCHAR(64))
|
||||
RETURNS INTEGER
|
||||
LANGUAGE plpgsql AS $BODY$
|
||||
DECLARE
|
||||
account_id INTEGER;
|
||||
BEGIN
|
||||
SELECT acc.id INTO account_id FROM auth.account acc WHERE acc.email = p_email;
|
||||
|
||||
IF account_id IS NULL OR account_id = 0 THEN
|
||||
INSERT INTO auth.account (
|
||||
email,
|
||||
passwordHash,
|
||||
role
|
||||
) VALUES (
|
||||
p_email,
|
||||
p_passwordHash,
|
||||
p_role
|
||||
) RETURNING id INTO account_id;
|
||||
ELSE
|
||||
-- 0 means there is a duplicate account
|
||||
account_id = 0;
|
||||
END IF;
|
||||
|
||||
RETURN account_id;
|
||||
END;
|
||||
$BODY$;
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose StatementBegin
|
||||
CREATE OR REPLACE FUNCTION auth.bh_verify_account(
|
||||
p_verification_token VARCHAR(128))
|
||||
RETURNS INTEGER
|
||||
LANGUAGE plpgsql AS $BODY$
|
||||
DECLARE
|
||||
account_id INTEGER;
|
||||
verification_id INTEGER;
|
||||
BEGIN
|
||||
SELECT
|
||||
id, accountid INTO verification_id, account_id
|
||||
FROM auth.account_verification
|
||||
WHERE verificationtoken = p_verification_token;
|
||||
|
||||
IF account_id IS NULL OR account_id = 0 THEN
|
||||
-- 0 means there is a duplicate account
|
||||
account_id = 0;
|
||||
ELSE
|
||||
UPDATE auth.account SET verifiedTs=NOW() WHERE id = account_id;
|
||||
DELETE FROM auth.account_verification WHERE id = verification_id;
|
||||
END IF;
|
||||
|
||||
RETURN account_id;
|
||||
END;
|
||||
$BODY$;
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose StatementBegin
|
||||
DO
|
||||
$do$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT FROM pg_catalog.pg_roles
|
||||
WHERE rolname = 'auth-service') THEN
|
||||
|
||||
CREATE USER "auth-service" WITH PASSWORD 'auth-service';
|
||||
END IF;
|
||||
END
|
||||
$do$;
|
||||
-- +goose StatementEnd
|
||||
|
||||
GRANT CONNECT ON DATABASE bh to "auth-service";
|
||||
GRANT USAGE ON SCHEMA auth TO "auth-service";
|
||||
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA auth TO "auth-service";
|
||||
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA auth TO "auth-service";
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- +goose Down
|
@ -0,0 +1,46 @@
|
||||
-- name: RegisterAccount :one
|
||||
SELECT auth.bh_register_account(
|
||||
sqlc.arg(email),
|
||||
sqlc.arg(passwordHash),
|
||||
sqlc.arg(role)
|
||||
);
|
||||
|
||||
-- name: GetAccountByEmailOrId :one
|
||||
SELECT
|
||||
id,
|
||||
createdTs,
|
||||
verifiedTs,
|
||||
email,
|
||||
role,
|
||||
enabled
|
||||
FROM auth.account
|
||||
WHERE
|
||||
email = sqlc.arg(email) OR id = sqlc.arg(accountId);
|
||||
|
||||
-- name: GetSessionAccount :one
|
||||
SELECT
|
||||
acc.id,
|
||||
acc.createdTs,
|
||||
acc.verifiedTs,
|
||||
acc.email,
|
||||
acc.role,
|
||||
acc.enabled
|
||||
FROM auth.account acc
|
||||
JOIN auth.sessions sess ON sess.accountId = acc.id
|
||||
WHERE sess.id = sqlc.arg(sessionId) AND acc.enabled = TRUE;
|
||||
|
||||
|
||||
-- name: VerifyAccount :one
|
||||
SELECT auth.bh_verify_account(
|
||||
sqlc.arg(token)
|
||||
);
|
||||
|
||||
|
||||
-- name: CreateSession :one
|
||||
INSERT INTO auth.sessions (
|
||||
accountId,
|
||||
createdTs
|
||||
) SELECT acc.id, NOW() FROM auth.account acc WHERE
|
||||
(acc.email = sqlc.arg(email) OR acc.id = sqlc.arg(accountId))
|
||||
AND acc.passwordHash = sqlc.arg(passwordHash)
|
||||
RETURNING *;
|
@ -0,0 +1,152 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/data/postgres"
|
||||
"github.com/jackc/pgx/v4"
|
||||
)
|
||||
|
||||
type (
|
||||
PGRunnerStorage struct {
|
||||
*postgres.Queries
|
||||
}
|
||||
|
||||
RegisterAccountInput struct {
|
||||
Email string
|
||||
PasswordHash string
|
||||
Role string
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
ErrSessionNotFound = errors.New("session not found")
|
||||
ErrAccountNotFound = errors.New("account not found by the provided email or id")
|
||||
ErrCouldNotCreateSession = errors.New("could not create session")
|
||||
)
|
||||
|
||||
func (db *PGRunnerStorage) RegisterAccount(ctx context.Context, in RegisterAccountInput) (accountId int, err error) {
|
||||
var accId int32
|
||||
if accId, err = db.Queries.RegisterAccount(ctx, postgres.RegisterAccountParams{
|
||||
Email: in.Email,
|
||||
Passwordhash: in.PasswordHash,
|
||||
Role: in.Role,
|
||||
}); err != nil {
|
||||
err = fmt.Errorf("could not register a new account with DB: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
accountId = int(accId)
|
||||
return
|
||||
}
|
||||
|
||||
type (
|
||||
GetAccountInput struct {
|
||||
AccountID int
|
||||
Email string
|
||||
}
|
||||
|
||||
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,
|
||||
Accountid: int32(in.AccountID),
|
||||
}); errors.Is(err, pgx.ErrNoRows) {
|
||||
err = ErrAccountNotFound
|
||||
return
|
||||
} else if err != nil {
|
||||
err = fmt.Errorf("could not execute GetSessionAccount query: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
out = AccountResult{
|
||||
ID: int(row.ID),
|
||||
Email: row.Email,
|
||||
Role: row.Role,
|
||||
Created: row.Createdts,
|
||||
Verified: row.Verifiedts.Time,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (db *PGRunnerStorage) GetAccountBySession(ctx context.Context, sessionID int) (out AccountResult, err error) {
|
||||
var row postgres.GetSessionAccountRow
|
||||
if row, err = db.Queries.GetSessionAccount(ctx, int32(sessionID)); errors.Is(err, pgx.ErrNoRows) {
|
||||
err = ErrSessionNotFound
|
||||
return
|
||||
} else if err != nil {
|
||||
err = fmt.Errorf("could not execute GetSessionAccount query: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
out = AccountResult{
|
||||
ID: int(row.ID),
|
||||
Email: row.Email,
|
||||
Role: row.Role,
|
||||
Created: row.Createdts,
|
||||
Verified: row.Verifiedts.Time,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type (
|
||||
CreateSessionInput struct {
|
||||
Email string
|
||||
PasswordHash string
|
||||
}
|
||||
|
||||
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, postgres.CreateSessionParams{
|
||||
Email: in.Email,
|
||||
Passwordhash: in.PasswordHash,
|
||||
}); errors.Is(err, pgx.ErrNoRows) {
|
||||
err = ErrCouldNotCreateSession
|
||||
return
|
||||
} else if err != nil {
|
||||
err = fmt.Errorf("could not execute query: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
out = Session{
|
||||
ID: int(session.ID),
|
||||
AccountID: int(session.Accountid),
|
||||
Created: session.Createdts,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type VerifyAccountInput struct {
|
||||
Token string
|
||||
}
|
||||
|
||||
func (db *PGRunnerStorage) VerifyAccount(ctx context.Context, in VerifyAccountInput) (err error) {
|
||||
err = errors.New("Unimplemented")
|
||||
return
|
||||
}
|
@ -0,0 +1,187 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/data"
|
||||
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
|
||||
)
|
||||
|
||||
type (
|
||||
Domain struct {
|
||||
Storage
|
||||
PasswordHasher
|
||||
}
|
||||
|
||||
HashedPassword []byte
|
||||
|
||||
PasswordHasher interface {
|
||||
Hash(string) HashedPassword
|
||||
Compare(string, HashedPassword) bool
|
||||
}
|
||||
|
||||
Storage interface {
|
||||
RegisterAccount(context.Context, data.RegisterAccountInput) (int, error)
|
||||
GetAccountBySession(context.Context, int) (data.AccountResult, 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,
|
||||
PasswordHash: ar.PasswordHash,
|
||||
Role: Role(ar.Role),
|
||||
Enabled: ar.Enabled,
|
||||
},
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type (
|
||||
LoginCommand struct {
|
||||
Email string
|
||||
Password string
|
||||
}
|
||||
LoggedIn struct {
|
||||
Account
|
||||
SessionID int
|
||||
Created time.Time
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidLogin = errors.New("invalid credentials provided")
|
||||
ErrInvalidSession = errors.New("invalid session id provided")
|
||||
)
|
||||
|
||||
func (d *Domain) Login(ctx context.Context, in LoginCommand) (out LoggedIn, err error) {
|
||||
kernel.TraceLog.Printf("%q login attempt", in.Email)
|
||||
var sess data.Session
|
||||
if sess, err = d.Storage.CreateSession(ctx, data.CreateSessionInput{
|
||||
Email: in.Email,
|
||||
PasswordHash: in.Password,
|
||||
}); errors.Is(err, data.ErrCouldNotCreateSession) {
|
||||
err = ErrInvalidLogin
|
||||
return
|
||||
} else if err != nil {
|
||||
kernel.ErrorLog.Printf("Error creating session in storage for %q: %w", in.Email, err)
|
||||
err = ErrInvalidLogin
|
||||
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 {
|
||||
SessionID int
|
||||
}
|
||||
)
|
||||
|
||||
func (d *Domain) GetAccount(ctx context.Context, in GetAccountCommand) (out Account, err error) {
|
||||
var ar data.AccountResult
|
||||
if ar, err = d.Storage.GetAccountBySession(ctx, in.SessionID); errors.Is(err, data.ErrSessionNotFound) {
|
||||
err = ErrInvalidSession
|
||||
return
|
||||
} else if err != nil {
|
||||
kernel.ErrorLog.Printf("Error getting account by session id: %w", err)
|
||||
err = fmt.Errorf("could not get account: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
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,166 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/data"
|
||||
)
|
||||
|
||||
func TestDomain_CreateAccount(t *testing.T) {
|
||||
SetupTest()
|
||||
|
||||
tests := map[string]struct {
|
||||
input CreateAccountCommand
|
||||
wantOut AccountCreated
|
||||
wantErr bool
|
||||
}{
|
||||
"new account": {
|
||||
input: CreateAccountCommand{
|
||||
Email: "test@example.com",
|
||||
Password: "password",
|
||||
},
|
||||
wantOut: AccountCreated{
|
||||
Account: Account{
|
||||
ID: 0,
|
||||
Email: "test@example.com",
|
||||
PasswordHash: "password",
|
||||
Enabled: true,
|
||||
Role: UserRole,
|
||||
},
|
||||
},
|
||||
},
|
||||
"no email": {
|
||||
input: CreateAccountCommand{
|
||||
Password: "password",
|
||||
Role: UserRole,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"no password": {
|
||||
input: CreateAccountCommand{
|
||||
Email: "test@example.com",
|
||||
Role: UserRole,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
"no inputs": {
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
c := ioc.Scope("CreateAccountTest")
|
||||
|
||||
Must(c.Provide(func(s Storage, ms *MockStorage) *Domain {
|
||||
return &Domain{Storage: s}
|
||||
}))
|
||||
|
||||
for name, v := range tests {
|
||||
params := v
|
||||
t.Run(name, func(t *testing.T) {
|
||||
Must(c.Invoke(func(sut *Domain, ms *MockStorage) {
|
||||
var a Account
|
||||
ms.RegisterAccountFunc = func(ctx context.Context, rai data.RegisterAccountInput) (int, error) {
|
||||
a = Account{
|
||||
Email: rai.Email,
|
||||
PasswordHash: rai.PasswordHash,
|
||||
Role: Role(rai.Role),
|
||||
Enabled: true,
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
ms.GetAccountFunc = func(ctx context.Context, gai data.GetAccountInput) (data.AccountResult, error) {
|
||||
if gai.Email != a.Email {
|
||||
return data.AccountResult{}, errors.New("error")
|
||||
}
|
||||
return data.AccountResult{
|
||||
Email: a.Email,
|
||||
PasswordHash: a.PasswordHash,
|
||||
Role: a.Role.String(),
|
||||
Enabled: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
out, err := sut.CreateAccount(context.Background(), params.input)
|
||||
if (err != nil) != params.wantErr {
|
||||
t.Errorf("Domain.CreateAccount() error = %v, wantErr %v", err, params.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(out, params.wantOut) {
|
||||
t.Errorf("Domain.CreateAccount():\n%+v\n, want:\n%+v", out, params.wantOut)
|
||||
}
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomain_Login(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
input LoginCommand
|
||||
wantOut LoggedIn
|
||||
wantErr bool
|
||||
}{
|
||||
"Login Valid User": {
|
||||
input: LoginCommand{
|
||||
Email: "test@example.com",
|
||||
Password: "password",
|
||||
},
|
||||
wantOut: LoggedIn{
|
||||
Account: Account{
|
||||
ID: 1,
|
||||
Email: "test@example.com",
|
||||
PasswordHash: "",
|
||||
Enabled: true,
|
||||
Role: UserRole,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c := ioc.Scope("LoginTest")
|
||||
|
||||
Must(c.Provide(func(s Storage) *Domain {
|
||||
return &Domain{Storage: s}
|
||||
}))
|
||||
|
||||
for name, v := range tests {
|
||||
params := v
|
||||
t.Run(name, func(t *testing.T) {
|
||||
Must(c.Invoke(func(sut *Domain, ms *MockStorage) {
|
||||
ms.CreateSessionFunc = func(ctx context.Context, in data.CreateSessionInput) (data.Session, error) {
|
||||
if strings.EqualFold(in.Email, "test@example.com") {
|
||||
return data.Session{AccountID: 1}, nil
|
||||
}
|
||||
|
||||
return data.Session{}, data.ErrCouldNotCreateSession
|
||||
}
|
||||
|
||||
ms.GetAccountFunc = func(ctx context.Context, gai data.GetAccountInput) (data.AccountResult, error) {
|
||||
if strings.EqualFold(gai.Email, "test@example.com") || gai.AccountID == 1 {
|
||||
return data.AccountResult{
|
||||
ID: 1,
|
||||
Email: "test@example.com",
|
||||
Role: UserRole.String(),
|
||||
Enabled: true,
|
||||
}, nil
|
||||
}
|
||||
return data.AccountResult{}, data.ErrSessionNotFound
|
||||
}
|
||||
|
||||
out, err := sut.Login(context.Background(), params.input)
|
||||
if (err != nil) != params.wantErr {
|
||||
t.Errorf("Domain.Login() error = %v, wantErr %v", err, params.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(out, params.wantOut) {
|
||||
t.Errorf("Domain.Login() = %v, want %v", out, params.wantOut)
|
||||
}
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package domain
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
UserRole = Role("BIDDER")
|
||||
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 Role `json:"role"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
ID int
|
||||
AccountID int
|
||||
Created time.Time
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/data"
|
||||
"go.uber.org/dig"
|
||||
)
|
||||
|
||||
var ioc dig.Container
|
||||
|
||||
func SetupTest() {
|
||||
ioc = *dig.New()
|
||||
|
||||
if err := ioc.Provide(func() (Storage, *MockStorage) {
|
||||
ms := &MockStorage{}
|
||||
return ms, ms
|
||||
}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Must panics is err is not nil
|
||||
func Must(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type MockStorage struct {
|
||||
RegisterAccountFunc func(context.Context, data.RegisterAccountInput) (int, error)
|
||||
GetAccountFunc func(context.Context, data.GetAccountInput) (data.AccountResult, error)
|
||||
CreateSessionFunc func(context.Context, data.CreateSessionInput) (data.Session, error)
|
||||
}
|
||||
|
||||
// CreateSession implements Storage.
|
||||
func (m *MockStorage) CreateSession(ctx context.Context, in data.CreateSessionInput) (data.Session, error) {
|
||||
if m.CreateSessionFunc == nil {
|
||||
panic("CreateSessionFunc is unset")
|
||||
}
|
||||
return m.CreateSessionFunc(ctx, in)
|
||||
}
|
||||
|
||||
// GetAccount implements Storage.
|
||||
func (m *MockStorage) GetAccount(ctx context.Context, in data.GetAccountInput) (data.AccountResult, error) {
|
||||
if m.GetAccountFunc == nil {
|
||||
panic("GetAccountFunc is unset")
|
||||
}
|
||||
|
||||
return m.GetAccountFunc(ctx, in)
|
||||
}
|
||||
|
||||
// GetAccountBySession implements Storage.
|
||||
func (m *MockStorage) GetAccountBySession(ctx context.Context, in int) (data.AccountResult, error) {
|
||||
panic("GetAccountBySession not implemented")
|
||||
}
|
||||
|
||||
// RegisterAccount implements Storage.
|
||||
func (m *MockStorage) RegisterAccount(ctx context.Context, in data.RegisterAccountInput) (int, error) {
|
||||
if m.RegisterAccountFunc == nil {
|
||||
panic("RegisterAccountFunc is unset")
|
||||
}
|
||||
|
||||
return m.RegisterAccountFunc(ctx, in)
|
||||
}
|
||||
|
||||
// VerifyAccount implements Storage.
|
||||
func (*MockStorage) VerifyAccount(context.Context, data.VerifyAccountInput) error {
|
||||
panic("unimplemented")
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
api "git.vdhsn.com/barretthousen/barretthousen/src/auth/api/grpc"
|
||||
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/domain"
|
||||
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func NewAuthServer(d *domain.Domain) func(grpcServer grpc.ServiceRegistrar, endpoint string) {
|
||||
return func(grpcServer grpc.ServiceRegistrar, endpoint string) {
|
||||
api.RegisterAuthServer(grpcServer, &authHandler{domain: d})
|
||||
}
|
||||
}
|
||||
|
||||
type authHandler struct {
|
||||
api.UnimplementedAuthServer
|
||||
domain *domain.Domain
|
||||
}
|
||||
|
||||
func (a *authHandler) CreateAccount(ctx context.Context, in *api.CreateAccountInput) (*api.Account, error) {
|
||||
kernel.TraceLog.Printf("Attempting to create user: %q - %q", in.Email, in.Password)
|
||||
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) (out *api.LoginResult, err error) {
|
||||
var result domain.LoggedIn
|
||||
if result, err = a.domain.Login(ctx, domain.LoginCommand{
|
||||
Email: in.Email,
|
||||
Password: in.Password,
|
||||
}); errors.Is(err, domain.ErrInvalidLogin) {
|
||||
err = status.Errorf(codes.PermissionDenied, "Email or password invalid.")
|
||||
return
|
||||
} else if err != nil {
|
||||
err = fmt.Errorf("could not login: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = grpc.SetHeader(ctx, metadata.New(map[string]string{
|
||||
"bh-session-id": fmt.Sprintf("%d", result.SessionID),
|
||||
})); err != nil {
|
||||
err = status.Errorf(codes.Internal, "could not set header")
|
||||
return
|
||||
}
|
||||
|
||||
out = &api.LoginResult{
|
||||
SessionId: fmt.Sprintf("%d", result.SessionID),
|
||||
Account: &api.Account{
|
||||
Id: int32(result.ID),
|
||||
Email: result.Email,
|
||||
Role: result.Role.String(),
|
||||
CreatedTs: timestamppb.New(result.Created),
|
||||
VerifiedTs: nil,
|
||||
},
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
func (a *authHandler) GetSession(ctx context.Context, in *api.SessionFilter) (out *api.Account, err error) {
|
||||
var result domain.Account
|
||||
if result, err = a.domain.GetAccount(ctx, domain.GetAccountCommand{
|
||||
SessionID: int(in.SessionId),
|
||||
}); err != nil {
|
||||
err = status.Error(codes.Unauthenticated, "forbidden")
|
||||
return
|
||||
}
|
||||
|
||||
out = &api.Account{
|
||||
Id: int32(result.ID),
|
||||
Email: result.Email,
|
||||
Role: result.Role.String(),
|
||||
CreatedTs: timestamppb.New(result.Created),
|
||||
VerifiedTs: nil,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal"
|
||||
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/data"
|
||||
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/data/postgres"
|
||||
"git.vdhsn.com/barretthousen/barretthousen/src/auth/internal/domain"
|
||||
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
|
||||
"github.com/jackc/pgx/v4/pgxpool"
|
||||
"go.uber.org/dig"
|
||||
)
|
||||
|
||||
type (
|
||||
authApp struct {
|
||||
LogLevel kernel.LogLevel `yaml:"log_level" yaml-default:"0"`
|
||||
Port int `yaml:"port" `
|
||||
DB_Service kernel.PostgresConnection `yaml:"db_service"`
|
||||
DB_Migrate kernel.PostgresConnection `yaml:"db_migrate"`
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
migrate = flag.Bool("migrate", false, "migrates postgres db")
|
||||
client = flag.String("client", "", "Runs this service in client mode, to invoke sync job. Takes GRPC endpoint")
|
||||
target = flag.String("target", "", "To be used with client mode. The target to sync")
|
||||
|
||||
//go:embed internal/data/postgres/migrations/*.sql
|
||||
dbMigrateScript embed.FS
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
kernel.Run(context.Background(), &authApp{
|
||||
LogLevel: kernel.LevelTrace,
|
||||
Port: 5001,
|
||||
})
|
||||
}
|
||||
|
||||
func (app *authApp) Start(ctx context.Context) error {
|
||||
if *migrate {
|
||||
if err := kernel.MigrateDB(ctx, app.DB_Migrate, dbMigrateScript, "auth"); err != nil {
|
||||
return fmt.Errorf("could not execute db migration: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
ioc := dig.New()
|
||||
var err error
|
||||
if err = ioc.Provide(func() kernel.PostgresConnection {
|
||||
return app.DB_Service
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ioc.Provide(func(pgCfg kernel.PostgresConnection) (*pgxpool.Pool, error) {
|
||||
return kernel.NewDBConnection(ctx, pgCfg)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ioc.Provide(func(pgConn *pgxpool.Pool) *postgres.Queries {
|
||||
return postgres.New(pgConn)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ioc.Provide(func(queries *postgres.Queries) domain.Storage {
|
||||
return &data.PGRunnerStorage{Queries: queries}
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = ioc.Provide(func(rs domain.Storage) *domain.Domain {
|
||||
return &domain.Domain{
|
||||
Storage: rs,
|
||||
}
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioc.Invoke(func(d *domain.Domain) error {
|
||||
authService := internal.NewAuthServer(d)
|
||||
|
||||
if _, err := kernel.StartGRPCServer(ctx, app.Port, authService); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (app *authApp) OnStop(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (app *authApp) GetLogLevel() kernel.LogLevel { return app.LogLevel }
|
@ -0,0 +1,101 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
interface FormValidations {
|
||||
email?: boolean;
|
||||
general?: boolean;
|
||||
}
|
||||
|
||||
export const validations: FormValidations = {};
|
||||
|
||||
let email: string = '';
|
||||
let password: string = '';
|
||||
let passwordConfirmation: string = '';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const execLogin = () =>
|
||||
dispatch('login', {
|
||||
email,
|
||||
password
|
||||
});
|
||||
|
||||
const execRegister = () => {
|
||||
if (password !== passwordConfirmation) {
|
||||
console.log("password and confirm don't match");
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch('register', {
|
||||
email,
|
||||
password
|
||||
});
|
||||
};
|
||||
|
||||
let showPassword = false;
|
||||
let showRegistration = false;
|
||||
|
||||
const revealPass = () => (showPassword = !showPassword);
|
||||
const toggleFormType = () => (showRegistration = !showRegistration);
|
||||
|
||||
$: ctaTxt = showRegistration ? 'Up' : 'In';
|
||||
$: toggleCtaTxt = !showRegistration ? 'Up' : 'In';
|
||||
$: ctaFunc = showRegistration ? execRegister : execLogin;
|
||||
</script>
|
||||
|
||||
<form class="flex" style="max-width: 600px;" on:submit|preventDefault={ctaFunc}>
|
||||
<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="px-2 py-1 border-r-0 grow invalid:border-red-500 invalid:border-2"
|
||||
type="email"
|
||||
required
|
||||
bind:value={email}
|
||||
/>
|
||||
<button class="border-l-0 px-2 py-1 disabled:text-gray-500">
|
||||
Sign {ctaTxt}
|
||||
</button>
|
||||
</span>
|
||||
<span class="flex">
|
||||
<input
|
||||
class="px-2 py-1 w-full"
|
||||
name="password"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
required
|
||||
placeholder={showRegistration ? 'Password' : ''}
|
||||
on:blur={() => (showPassword = false)}
|
||||
on:change={(evt) => (password = evt?.target?.value)}
|
||||
/>
|
||||
{#if showRegistration}
|
||||
<input
|
||||
class="px-2 py-1 border-r-0 invalid:border-red-500 invalid:border-2"
|
||||
name="confirm_password"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
pattern={password}
|
||||
required
|
||||
placeholder="Confirm Password"
|
||||
on:blur={() => (showPassword = false)}
|
||||
on:change={(evt) => (passwordConfirmation = evt?.target?.value)}
|
||||
/>
|
||||
|
||||
<button
|
||||
class="border-l-0 px-2 py-1 w-16"
|
||||
tabindex="-1"
|
||||
on:click|stopPropagation={revealPass}
|
||||
>
|
||||
{showPassword ? 'Hide' : 'Show'}
|
||||
</button>
|
||||
{/if}
|
||||
</span>
|
||||
</span>
|
||||
<button
|
||||
class="w-20 bg-bh-gold text-bh-black border-bh-black"
|
||||
on:click|stopPropagation={toggleFormType}
|
||||
>
|
||||
I Want to Sign {toggleCtaTxt}
|
||||
</button>
|
||||
</form>
|
@ -0,0 +1,116 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { env } from '$env/dynamic/public';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
const API_HOST = `${browser ? '' : env.BH_CLIENT_INTERNAL_API_HOST}/api/v1`;
|
||||
|
||||
interface SessionInfo {
|
||||
sessionId?: string;
|
||||
account?: {
|
||||
id: string;
|
||||
email: string;
|
||||
role: 'BIDDER' | 'USER' | 'ADMINISTRATOR' | 'ANONYMOUS';
|
||||
createdTs: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const getSession = () => {
|
||||
const strSession = localStorage.getItem('bh-session');
|
||||
if (strSession) {
|
||||
const data = JSON.parse(strSession);
|
||||
if (data?.sessionId) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
export const session = writable<SessionInfo>(undefined, (set) => {
|
||||
const session = getSession();
|
||||
if (session) {
|
||||
set(session);
|
||||
}
|
||||
});
|
||||
|
||||
interface LoginAction {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export const loginAction = async ({ email, password }: LoginAction): Promise<SessionInfo> => {
|
||||
try {
|
||||
const sessionStr = localStorage.getItem('bh-session');
|
||||
if (sessionStr) {
|
||||
const data = JSON.parse(sessionStr);
|
||||
if (data.sessionId) {
|
||||
console.log('already authenticated');
|
||||
session.set(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
localStorage.removeItem('bh-session');
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
new Request(`${API_HOST}/user`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
|
||||
if (data.sessionId) {
|
||||
localStorage.setItem('bh-session', JSON.stringify(data));
|
||||
return data;
|
||||
}
|
||||
|
||||
console.trace("got this on login attempt:", data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
interface RegisterAction {
|
||||
email: string;
|
||||
password: string;
|
||||
role?: string;
|
||||
}
|
||||
export const registerAction = async ({ email, password, role }: RegisterAction) => {
|
||||
try {
|
||||
const response = await fetch(
|
||||
new Request(`${API_HOST}/user`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
password,
|
||||
role: role || 'USER'
|
||||
})
|
||||
})
|
||||
);
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
export const logoutAction = () => {
|
||||
localStorage.removeItem('bh-session');
|
||||
session.set({});
|
||||
};
|
Loading…
Reference in new issue