added login and checking of roles to admin page
ci.vdhsn.com/push Build is failing
Details
ci.vdhsn.com/push Build is failing
Details
parent
abb8e565cd
commit
e891ada9e8
|
|
@ -54,3 +54,4 @@ stringData:
|
|||
port: 80
|
||||
endpoints:
|
||||
runner: runner:5001
|
||||
auth: auth:5001
|
||||
|
|
|
|||
|
|
@ -9,3 +9,4 @@ stringData:
|
|||
access_control_allow_origin: "beta.admin.barretthousen.com"
|
||||
endpoints:
|
||||
runner: runner-beta:5001
|
||||
auth: auth-beta:5001
|
||||
|
|
|
|||
|
|
@ -8,3 +8,4 @@ stringData:
|
|||
port: 80
|
||||
endpoints:
|
||||
runner: runner-local:5001
|
||||
auth: auth-local:5001
|
||||
|
|
|
|||
|
|
@ -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({});
|
||||
};
|
||||
|
|
@ -1,5 +1,34 @@
|
|||
<script lang="ts">
|
||||
import '../app.css';
|
||||
|
||||
import AuthForm from '$lib/AuthForm.svelte';
|
||||
|
||||
import { loginAction, logoutAction, registerAction, session } from '$lib/state';
|
||||
|
||||
async function onLogin(evt: CustomEvent) {
|
||||
const { email, password } = evt.detail;
|
||||
await loginAction({ email, password });
|
||||
}
|
||||
|
||||
async function onLogout() {
|
||||
logoutAction();
|
||||
}
|
||||
|
||||
interface SessionInfo {
|
||||
sessionId?: string;
|
||||
account?: {
|
||||
id: string;
|
||||
email: string;
|
||||
role: 'BIDDER' | 'USER' | 'ADMINISTRATOR' | 'ANONYMOUS';
|
||||
createdTs: string;
|
||||
};
|
||||
}
|
||||
|
||||
let sessionVal: SessionInfo;
|
||||
|
||||
session.subscribe((v) => (sessionVal = v));
|
||||
|
||||
$: isLoggedIn = !!sessionVal?.sessionId;
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
@ -26,8 +55,19 @@
|
|||
<li class="flex grow justify-center">
|
||||
<span class="flex grow" style="max-width: 75%;"> Admin Stuff </span>
|
||||
</li>
|
||||
<li class="px-6">
|
||||
<li class="pr-5">
|
||||
<!-- <span>I want email alerts!</span> -->
|
||||
{#if !isLoggedIn}
|
||||
<AuthForm on:login={onLogin} />
|
||||
{:else}
|
||||
<span>
|
||||
{sessionVal.account?.email}
|
||||
</span>
|
||||
<button
|
||||
class="bg-bh-gold border-bh-black px-2 py-1 text-bh-black"
|
||||
on:click|stopPropagation={onLogout}>Log out</button
|
||||
>
|
||||
{/if}
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
import { fade } from 'svelte/transition';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { getSession } from '$lib/state';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
|
|
@ -27,7 +28,8 @@
|
|||
new Request('/api/v1/sync', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'bh-session-id': getSession()?.sessionId
|
||||
},
|
||||
body: JSON.stringify({ targetSite: detail.target })
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { browser } from '$app/environment';
|
||||
import type { PageLoad } from './$types';
|
||||
import { env } from '$env/dynamic/public';
|
||||
import { getSession } from '$lib/state';
|
||||
|
||||
const API_HOST = `${browser ? '' : env.BH_CLIENT_INTERNAL_API_HOST}/api/v1`;
|
||||
|
||||
|
|
@ -26,8 +27,21 @@ interface ScrapeStatusPageData {
|
|||
export const load = (async ({ fetch, url }): Promise<ScrapeStatusPageData> => {
|
||||
const searchParams = new SearchParameters(url);
|
||||
const limit = searchParams.getLimit();
|
||||
|
||||
console.log(getSession());
|
||||
try {
|
||||
const response = await fetch(API_HOST + `/sync${searchParams.toQueryString()}`);
|
||||
const response = await fetch(
|
||||
new Request(API_HOST + `/sync${searchParams.toQueryString()}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'bh-session-id': getSession()?.sessionId,
|
||||
}
|
||||
}
|
||||
|
||||
)
|
||||
|
||||
);
|
||||
const { active, complete, activeTotal, completeTotal, total, page } = await response.json();
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
}
|
||||
|
|
@ -33,6 +33,12 @@ service Auth {
|
|||
get: "/v1/user"
|
||||
};
|
||||
}
|
||||
|
||||
rpc GetSession(SessionFilter) returns (Account) { }
|
||||
}
|
||||
|
||||
message SessionFilter {
|
||||
int32 sessionId = 1;
|
||||
}
|
||||
|
||||
message CreateAccountInput {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ SELECT auth.bh_register_account(
|
|||
sqlc.arg(role)
|
||||
);
|
||||
|
||||
-- name: GetAccountByEmailOrID :one
|
||||
-- name: GetAccountByEmailOrId :one
|
||||
SELECT
|
||||
id,
|
||||
createdTs,
|
||||
|
|
@ -13,8 +13,21 @@ SELECT
|
|||
email,
|
||||
role,
|
||||
enabled
|
||||
FROM auth.account WHERE
|
||||
email = sqlc.arg(email) OR id = sqlc.arg(id);
|
||||
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
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ type (
|
|||
)
|
||||
|
||||
var (
|
||||
ErrAccountNotFound = errors.New("account not found")
|
||||
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")
|
||||
)
|
||||
|
||||
|
|
@ -44,8 +45,8 @@ func (db *PGRunnerStorage) RegisterAccount(ctx context.Context, in RegisterAccou
|
|||
|
||||
type (
|
||||
GetAccountInput struct {
|
||||
Email string
|
||||
AccountID int
|
||||
Email string
|
||||
}
|
||||
|
||||
AccountResult struct {
|
||||
|
|
@ -60,15 +61,37 @@ type (
|
|||
)
|
||||
|
||||
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),
|
||||
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 GetAccount query: %w", err)
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ type (
|
|||
|
||||
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
|
||||
|
|
@ -103,7 +104,10 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
var ErrInvalidLogin = errors.New("invalid credentials provided")
|
||||
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)
|
||||
|
|
@ -145,13 +149,20 @@ func (d *Domain) Login(ctx context.Context, in LoginCommand) (out LoggedIn, err
|
|||
}
|
||||
|
||||
type (
|
||||
GetAccountCommand struct{}
|
||||
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.GetAccount(ctx, data.GetAccountInput{}); err != nil {
|
||||
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{
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ func TestDomain_Login(t *testing.T) {
|
|||
Enabled: true,
|
||||
}, nil
|
||||
}
|
||||
return data.AccountResult{}, data.ErrAccountNotFound
|
||||
return data.AccountResult{}, data.ErrSessionNotFound
|
||||
}
|
||||
|
||||
out, err := sut.Login(context.Background(), params.input)
|
||||
|
|
|
|||
|
|
@ -50,6 +50,11 @@ func (m *MockStorage) GetAccount(ctx context.Context, in data.GetAccountInput) (
|
|||
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 {
|
||||
|
|
|
|||
|
|
@ -86,3 +86,23 @@ func (a *authHandler) VerifyAccount(ctx context.Context, in *api.VerificationPar
|
|||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ go 1.19
|
|||
require (
|
||||
git.vdhsn.com/barretthousen/barretthousen/src/lib v1.0.0
|
||||
git.vdhsn.com/barretthousen/barretthousen/src/runner v1.0.0
|
||||
git.vdhsn.com/barretthousen/barretthousen/src/auth v1.0.0
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2
|
||||
google.golang.org/grpc v1.55.0
|
||||
)
|
||||
|
|
@ -39,3 +40,5 @@ require (
|
|||
replace git.vdhsn.com/barretthousen/barretthousen/src/lib v1.0.0 => ../lib
|
||||
|
||||
replace git.vdhsn.com/barretthousen/barretthousen/src/runner v1.0.0 => ../runner
|
||||
|
||||
replace git.vdhsn.com/barretthousen/barretthousen/src/auth v1.0.0 => ../auth
|
||||
|
|
|
|||
|
|
@ -7,9 +7,12 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
authApi "git.vdhsn.com/barretthousen/barretthousen/src/auth/api"
|
||||
aapi "git.vdhsn.com/barretthousen/barretthousen/src/auth/api/grpc"
|
||||
"git.vdhsn.com/barretthousen/barretthousen/src/lib/kernel"
|
||||
api "git.vdhsn.com/barretthousen/barretthousen/src/runner/api/grpc"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"go.uber.org/dig"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/backoff"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
|
@ -20,43 +23,108 @@ type ProxyAdminApp struct {
|
|||
Port int `yaml:"port" `
|
||||
Endpoints struct {
|
||||
Runner string `yaml:"runner" `
|
||||
Auth string `yaml:"auth"`
|
||||
} `yaml:"endpoints"`
|
||||
}
|
||||
|
||||
type AuthService interface {
|
||||
CheckSession(context.Context, authApi.CheckSessionParams) (authApi.Account, error)
|
||||
}
|
||||
|
||||
func (app *ProxyAdminApp) Start(ctx context.Context) error {
|
||||
grpcMux := runtime.NewServeMux()
|
||||
err := api.RegisterRunnerHandlerFromEndpoint(ctx, grpcMux, app.Endpoints.Runner, []grpc.DialOption{
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithConnectParams(grpc.ConnectParams{
|
||||
Backoff: backoff.Config{
|
||||
MaxDelay: time.Second * 3,
|
||||
},
|
||||
MinConnectTimeout: time.Second,
|
||||
}),
|
||||
})
|
||||
if err != nil {
|
||||
ioc := dig.New()
|
||||
|
||||
var err error
|
||||
if err = ioc.Provide(func() *runtime.ServeMux {
|
||||
return runtime.NewServeMux()
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kernel.TraceLog.Printf("%+v", app)
|
||||
|
||||
httpServer := &http.Server{
|
||||
Addr: fmt.Sprintf("0.0.0.0:%d", app.Port),
|
||||
ReadHeaderTimeout: time.Second,
|
||||
Handler: http.StripPrefix("/api", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
kernel.TraceLog.Printf("{ \"Client\": \"%s\", \"Path\":\"%s\", \"User-Agent\":\"%s\", \"Host\":\"%s\", \"Origin\":\"%s\"} ", r.RemoteAddr, r.URL, r.UserAgent(), r.Host, r.Header.Get("Origin"))
|
||||
|
||||
if strings.HasPrefix(r.Host, "proxy-") {
|
||||
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
||||
}
|
||||
|
||||
grpcMux.ServeHTTP(w, r)
|
||||
})),
|
||||
if err = ioc.Provide(func() (grpc.ClientConnInterface, error) {
|
||||
return kernel.DialGRPC(app.Endpoints.Auth)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kernel.InfoLog.Printf("Starting HTTP proxy @ %q", httpServer.Addr)
|
||||
if err = ioc.Provide(func(conn grpc.ClientConnInterface) AuthService {
|
||||
return authApi.NewAuthServiceClient(conn)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return httpServer.ListenAndServe()
|
||||
if err = ioc.Invoke(func(grpcMux *runtime.ServeMux, authClient AuthService) error {
|
||||
// TODO: refactor into kernel package
|
||||
if err := api.RegisterRunnerHandlerFromEndpoint(ctx, grpcMux, app.Endpoints.Runner, []grpc.DialOption{
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithConnectParams(grpc.ConnectParams{
|
||||
Backoff: backoff.Config{
|
||||
MaxDelay: time.Second * 3,
|
||||
},
|
||||
MinConnectTimeout: time.Second,
|
||||
}),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: refactor into kernel package
|
||||
if err := aapi.RegisterAuthHandlerFromEndpoint(ctx, grpcMux, app.Endpoints.Auth, []grpc.DialOption{
|
||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithConnectParams(grpc.ConnectParams{
|
||||
Backoff: backoff.Config{
|
||||
MaxDelay: time.Second * 3,
|
||||
},
|
||||
MinConnectTimeout: time.Second,
|
||||
}),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kernel.TraceLog.Printf("%+v", app)
|
||||
httpServer := &http.Server{
|
||||
Addr: fmt.Sprintf("0.0.0.0:%d", app.Port),
|
||||
ReadHeaderTimeout: time.Second,
|
||||
Handler: http.StripPrefix("/api", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
kernel.TraceLog.Printf("{ \"Client\": \"%s\", \"Path\":\"%s\", \"User-Agent\":\"%s\", \"Host\":\"%s\", \"Origin\":\"%s\"} ", r.RemoteAddr, r.URL, r.UserAgent(), r.Host, r.Header.Get("Origin"))
|
||||
|
||||
// TODO: move to a middleware package
|
||||
if strings.HasPrefix(r.Host, "proxy-") {
|
||||
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
||||
}
|
||||
|
||||
// TODO: move to a middleware package
|
||||
if !(strings.HasSuffix(r.URL.Path, "user") && r.Method == http.MethodPost) {
|
||||
sessIdStr := r.Header.Get("bh-session-id")
|
||||
kernel.TraceLog.Printf("session %s", sessIdStr)
|
||||
account, err := authClient.CheckSession(r.Context(), authApi.CheckSessionParams{
|
||||
SessionID: sessIdStr,
|
||||
})
|
||||
if err != nil {
|
||||
kernel.ErrorLog.Printf("error calling auth service: %v", err)
|
||||
http.Error(w, "must be logged in as admin", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
kernel.TraceLog.Printf("{ \"session-id\":\"%s\", \"email\":\"%s\", \"accountId\":\"%d\", \"role\":\"%s\"}", sessIdStr, account.Email, account.ID, account.Role)
|
||||
if account.Role != "ADMINISTRATOR" {
|
||||
http.Error(w, "must be administrator", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
grpcMux.ServeHTTP(w, r)
|
||||
})),
|
||||
}
|
||||
|
||||
kernel.InfoLog.Printf("Starting HTTP proxy @ %q", httpServer.Addr)
|
||||
|
||||
return httpServer.ListenAndServe()
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *ProxyAdminApp) OnStop(ctx context.Context) {
|
||||
|
|
|
|||
|
|
@ -14,14 +14,24 @@ interface SessionInfo {
|
|||
};
|
||||
}
|
||||
|
||||
export const session = writable<SessionInfo>(undefined, (set) => {
|
||||
export const getSession = () => {
|
||||
const strSession = localStorage.getItem('bh-session');
|
||||
if (strSession) {
|
||||
const data = JSON.parse(strSession);
|
||||
if (data?.sessionId) {
|
||||
set(data);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
export const session = writable<SessionInfo>(undefined, (set) => {
|
||||
const session = getSession();
|
||||
if (session) {
|
||||
set(session);
|
||||
}
|
||||
});
|
||||
|
||||
interface LoginAction {
|
||||
|
|
|
|||
Loading…
Reference in New Issue