Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
gbdubs committed Nov 14, 2023
1 parent 5ba55e0 commit 7e3091b
Show file tree
Hide file tree
Showing 18 changed files with 213 additions and 97 deletions.
2 changes: 1 addition & 1 deletion cmd/server/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ go_library(
"//dockertask",
"//oapierr",
"//openapi:pacta_generated",
"//pacta",
"//secrets",
"//session",
"//task",
"@com_github_azure_azure_sdk_for_go_sdk_azcore//:azcore",
"@com_github_azure_azure_sdk_for_go_sdk_azidentity//:azidentity",
Expand Down
59 changes: 2 additions & 57 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import (
"github.com/RMI/pacta/dockertask"
"github.com/RMI/pacta/oapierr"
oapipacta "github.com/RMI/pacta/openapi/pacta"
"github.com/RMI/pacta/pacta"
"github.com/RMI/pacta/secrets"
"github.com/RMI/pacta/session"
"github.com/RMI/pacta/task"
"github.com/Silicon-Ally/cryptorand"
"github.com/Silicon-Ally/zaphttplog"
Expand Down Expand Up @@ -318,7 +318,7 @@ func run(args []string) error {

jwtauth.Verifier(jwtauth.New("EdDSA", nil, jwKey)),
jwtauth.Authenticator,
addUserIdentityToContextIfLoggedIn(logger, db),
session.WithAuthn(logger, db),

oapimiddleware.OapiRequestValidator(pactaSwagger),

Expand Down Expand Up @@ -386,61 +386,6 @@ func rateLimitMiddleware(maxReq int, windowLength time.Duration) func(http.Handl
}))
}

func addUserIdentityToContextIfLoggedIn(logger *zap.Logger, db *sqldb.DB) func(http.Handler) http.Handler {
fn := func(c context.Context) (context.Context, error) {
token, _, err := jwtauth.FromContext(c)
if err != nil {
return nil, fmt.Errorf("error getting authorization token: %w", err)
}
if token == nil {
return nil, fmt.Errorf("nil authorization token")
}
emailsClaim, ok := token.PrivateClaims()["emails"]
if !ok {
return nil, fmt.Errorf("no email claim in token")
}
emails, ok := emailsClaim.([]interface{})
if !ok || len(emails) == 0 {
return nil, fmt.Errorf("couldn't find email claim in token: %T", emailsClaim)
}
// TODO(#18) Handle Multiple Emails in the Token Claims gracefully
if len(emails) > 1 {
return nil, fmt.Errorf("multiple emails in token: %+v", emails)
}
email, ok := emails[0].(string)
if !ok {
return nil, fmt.Errorf("wrong type for email claim: %T", emails[0])
}
canonical, err := pacta.CanonicalizeEmail(email)
if err != nil {
return nil, fmt.Errorf("invalid email on token: %q", email)
}
authnID := token.Subject()
if authnID == "" {
return nil, fmt.Errorf("couldn't find authn id in jwt")
}
user, err := db.GetOrCreateUserByAuthn(db.NoTxn(c), pacta.AuthnMechanism_EmailAndPass, authnID, email, canonical)
if err != nil {
return nil, fmt.Errorf("failed to get user by authn: %w", err)
}
return jwtauth.WithUserId(c, string(user.ID)), nil
}

return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, err := fn(r.Context())
if err != nil {
// Optionally log errors here when debugging authentication access.
// logger.Warn("couldn't authenticate", zap.Error(err))
// http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
next.ServeHTTP(w, r)
return
}
next.ServeHTTP(w, r.WithContext(ctx))
})
}
}

func findFirstInClaims(claims map[string]any, keys ...string) (string, error) {
for _, k := range keys {
v, ok := claims[k]
Expand Down
1 change: 1 addition & 0 deletions cmd/server/pactasrv/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ go_library(
"//oapierr",
"//openapi:pacta_generated",
"//pacta",
"//session",
"//task",
"@com_github_go_chi_jwtauth_v5//:jwtauth",
"@com_github_google_uuid//:uuid",
Expand Down
7 changes: 6 additions & 1 deletion cmd/server/pactasrv/initiative_invitation.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,13 @@ func (s *Server) GetInitiativeInvitation(ctx context.Context, request api.GetIni
}

// Claims this initiative invitation, if it exists
// (POST /initiative-invitation/{id})
// (POST /initiative-invitation/{id}:claim)
func (s *Server) ClaimInitiativeInvitation(ctx context.Context, request api.ClaimInitiativeInvitationRequestObject) (api.ClaimInitiativeInvitationResponseObject, error) {
userID, err := getUserID(ctx)
if err != nil {
return nil, err
}
var customErr api.ClaimInitiativeInvitationResponseObject
err = s.DB.Transactional(ctx, func(tx db.Tx) error {
ii, err := s.DB.InitiativeInvitation(tx, pacta.InitiativeInvitationID(request.Id))
if err != nil {
Expand All @@ -80,6 +81,7 @@ func (s *Server) ClaimInitiativeInvitation(ctx context.Context, request api.Clai
// We may want to log this, though.
return nil
} else {
customErr = api.ClaimInitiativeInvitation409Response{}
return fmt.Errorf("initiative is already used: %+v", ii)
}
}
Expand All @@ -97,6 +99,9 @@ func (s *Server) ClaimInitiativeInvitation(ctx context.Context, request api.Clai
return nil
})
if err != nil {
if customErr != nil {
return customErr, nil
}
return nil, oapierr.Internal("failed to claim initiative invitation", zap.Error(err))
}
return api.ClaimInitiativeInvitation204Response{}, nil
Expand Down
6 changes: 3 additions & 3 deletions cmd/server/pactasrv/pactasrv.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
"github.com/RMI/pacta/db"
"github.com/RMI/pacta/oapierr"
"github.com/RMI/pacta/pacta"
"github.com/RMI/pacta/session"
"github.com/RMI/pacta/task"
"github.com/go-chi/jwtauth/v5"
"go.uber.org/zap"
)

Expand Down Expand Up @@ -67,7 +67,7 @@ type DB interface {
CreatePortfolioInitiativeMembership(tx db.Tx, pim *pacta.PortfolioInitiativeMembership) error
DeletePortfolioInitiativeMembership(tx db.Tx, pid pacta.PortfolioID, iid pacta.InitiativeID) error

GetOrCreateUserByAuthn(tx db.Tx, authnMechanism pacta.AuthnMechanism, authnID, enteredEmail, canonicalEmail string) (*pacta.User, error)
GetOrCreateUserByAuthn(tx db.Tx, mech pacta.AuthnMechanism, authnID, email, canonicalEmail string) (*pacta.User, error)
User(tx db.Tx, id pacta.UserID) (*pacta.User, error)
Users(tx db.Tx, ids []pacta.UserID) (map[pacta.UserID]*pacta.User, error)
UpdateUser(tx db.Tx, id pacta.UserID, mutations ...db.UpdateUserFn) error
Expand Down Expand Up @@ -118,7 +118,7 @@ func dereference[T any](ts []*T, e error) ([]T, error) {
}

func getUserID(ctx context.Context) (pacta.UserID, error) {
userID, err := jwtauth.UserIDFromContext(ctx)
userID, err := session.UserIDFromContext(ctx)
if err != nil {
return "", oapierr.Unauthorized("error getting authorization token", zap.Error(err))
}
Expand Down
48 changes: 48 additions & 0 deletions cmd/server/pactasrv/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package pactasrv

import (
"context"
"fmt"

"github.com/RMI/pacta/cmd/server/pactasrv/conv"
"github.com/RMI/pacta/db"
"github.com/RMI/pacta/oapierr"
api "github.com/RMI/pacta/openapi/pacta"
"github.com/RMI/pacta/pacta"
"github.com/go-chi/jwtauth/v5"
"go.uber.org/zap"
)

Expand Down Expand Up @@ -84,3 +86,49 @@ func (s *Server) FindUserByMe(ctx context.Context, request api.FindUserByMeReque
}
return api.FindUserByMe200JSONResponse(*result), nil
}

// a callback after login to create or return the user
// (POST /user/authentication-followup)
func (s *Server) UserAuthenticationFollowup(ctx context.Context, _request api.UserAuthenticationFollowupRequestObject) (api.UserAuthenticationFollowupResponseObject, error) {
token, _, err := jwtauth.FromContext(ctx)
if err != nil {
return nil, oapierr.BadRequest("error getting authorization token", zap.Error(err))
}
if token == nil {
return nil, oapierr.BadRequest("nil authorization token")
}
emailsClaim, ok := token.PrivateClaims()["emails"]
if !ok {
return nil, oapierr.BadRequest("no email claim in token")
}
emails, ok := emailsClaim.([]interface{})
if !ok || len(emails) == 0 {
return nil, oapierr.BadRequest("couldn't find email claim in token", zap.String("emails_claim_type", fmt.Sprintf("%T", emailsClaim)))
}
// TODO(#18) Handle Multiple Emails in the Token Claims gracefully
if len(emails) > 1 {
return nil, oapierr.BadRequest(fmt.Sprintf("multiple emails in token: %+v", emails))
}
email, ok := emails[0].(string)
if !ok {
return nil, oapierr.BadRequest("wrong type for email claim", zap.String("email_claim_type", fmt.Sprintf("%T", emails[0])))
}
canonical, err := pacta.CanonicalizeEmail(email)
if err != nil {
return nil, oapierr.BadRequest(fmt.Sprintf("invalid email: %q", email), zap.String("email", email), zap.Error(err))
}
authnID := token.Subject()
if authnID == "" {
return nil, oapierr.BadRequest("couldn't find authn id in jwt")
}
user, err := s.DB.GetOrCreateUserByAuthn(s.DB.NoTxn(ctx), pacta.AuthnMechanism_EmailAndPass, authnID, email, canonical)
if err != nil {
return nil, fmt.Errorf("failed to GetOrCreateUser by authn: %w", err)
}
result, err := conv.UserToOAPI(user)
if err != nil {
return nil, err
}
return api.UserAuthenticationFollowup200JSONResponse(*result), nil

}
4 changes: 2 additions & 2 deletions db/sqldb/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (d *DB) User(tx db.Tx, id pacta.UserID) (*pacta.User, error) {
return exactlyOne("user", id, us)
}

func (d *DB) userByAuthn(tx db.Tx, authnMechanism pacta.AuthnMechanism, authnID string) (*pacta.User, error) {
func (d *DB) UserByAuthn(tx db.Tx, authnMechanism pacta.AuthnMechanism, authnID string) (*pacta.User, error) {
rows, err := d.query(tx, `
SELECT `+userSelectColumns+`
FROM pacta_user
Expand All @@ -55,7 +55,7 @@ func (d *DB) userByAuthn(tx db.Tx, authnMechanism pacta.AuthnMechanism, authnID
func (d *DB) GetOrCreateUserByAuthn(tx db.Tx, authnMechanism pacta.AuthnMechanism, authnID, enteredEmail, canonicalEmail string) (*pacta.User, error) {
var user *pacta.User
err := d.RunOrContinueTransaction(tx, func(tx db.Tx) error {
u, err := d.userByAuthn(tx, authnMechanism, authnID)
u, err := d.UserByAuthn(tx, authnMechanism, authnID)
if err == nil {
user = u
return nil
Expand Down
4 changes: 2 additions & 2 deletions db/sqldb/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func TestcreateUser(t *testing.T) {
}

// Read by Authn
actual, err = tdb.userByAuthn(tx, u.AuthnMechanism, u.AuthnID)
actual, err = tdb.UserByAuthn(tx, u.AuthnMechanism, u.AuthnID)
if err != nil {
t.Fatalf("getting user by authn: %w", err)
}
Expand Down Expand Up @@ -230,7 +230,7 @@ func TestDeleteUser(t *testing.T) {
}

// Read by Authn
_, err = tdb.userByAuthn(tx, u.AuthnMechanism, u.AuthnID)
_, err = tdb.UserByAuthn(tx, u.AuthnMechanism, u.AuthnID)
if err == nil {
t.Fatalf("expected error, got nil")
}
Expand Down
7 changes: 2 additions & 5 deletions frontend/components/LinkButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,8 @@ const buttonClass = computed(() => {
'p-button-loading-label-only': props.loading !== undefined && props.icon === undefined && props.label !== undefined,
'no-underline': true,
'click-does-nothing': disabled.value,
}
if (isActive.value) {
result[props.activeClass] = true
} else {
result[props.inactiveClass] = true
[props.activeClass]: isActive.value,
[props.inactiveClass]: !isActive.value,
}
return result
})
Expand Down
2 changes: 1 addition & 1 deletion frontend/components/initiative/Toolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const props = defineProps<Props>()
const isManager = computed(() => {
const mm = maybeMe.value
return (!!mm && props.initiativeUserRelationships.some(r => r.manager && r.userId === mm.id)) || true
return (!!mm && props.initiativeUserRelationships.some(r => r.manager && r.userId === mm.id))
})
const isMember = computed(() => {
const mm = maybeMe.value
Expand Down
30 changes: 16 additions & 14 deletions frontend/composables/useMSAL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const useMSAL = async () => {
}

const router = useRouter()
const { userClientWithAuth } = useAPI()
const { userClientWithAuth, pactaClientWithAuth } = useAPI()
const localePath = useLocalePath()

const { $msal: { msalConfig, b2cPolicies } } = useNuxtApp()
Expand Down Expand Up @@ -210,19 +210,6 @@ export const useMSAL = async () => {
return filteredAccounts[0]
})

const signIn = () => {
if (!instance.value) {
return Promise.reject(new Error('MSAL instance was not yet initialized'))
}

const req = { scopes }
return instance.value.loginPopup(req)
.then(handleResponse)
.catch((err) => {
console.log('useMSAL.loginPopup', err)
})
}

const getToken = () => {
if (!instance.value) {
return Promise.reject(new Error('MSAL instance was not yet initialized'))
Expand Down Expand Up @@ -252,6 +239,21 @@ export const useMSAL = async () => {
.then(handleResponse)
}

const signIn = () => {
if (!instance.value) {
return Promise.reject(new Error('MSAL instance was not yet initialized'))
}

const req = { scopes }
return instance.value.loginPopup(req)
.then(handleResponse)
.then(getToken)
.then(token => pactaClientWithAuth(token.idToken).userAuthenticationFollowup())
.catch((err) => {
console.log('useMSAL.loginPopup', err)
})
}

const createAPIKey = (): Promise<APIKey> => {
return getToken()
.then((response) => {
Expand Down
4 changes: 2 additions & 2 deletions frontend/openapi/generated/pacta/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ export type { NewPortfolioAsset } from './models/NewPortfolioAsset';
export type { PactaVersion } from './models/PactaVersion';
export type { PactaVersionChanges } from './models/PactaVersionChanges';
export type { PactaVersionCreate } from './models/PactaVersionCreate';
export type { ProcessPortfolioRequest } from './models/ProcessPortfolioRequest';
export type { ProcessPortfolioResponse } from './models/ProcessPortfolioResponse';
export type { ProcessPortfolioReq } from './models/ProcessPortfolioReq';
export type { ProcessPortfolioResp } from './models/ProcessPortfolioResp';
export { User } from './models/User';
export { UserChanges } from './models/UserChanges';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/* tslint:disable */
/* eslint-disable */

export type ProcessPortfolioRequest = {
export type ProcessPortfolioReq = {
asset_ids: Array<string>;
};

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/* tslint:disable */
/* eslint-disable */

export type ProcessPortfolioResponse = {
export type ProcessPortfolioResp = {
/**
* The ID of the async task for processing the portfoio
*/
Expand Down
Loading

0 comments on commit 7e3091b

Please sign in to comment.