-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implements findUserByMe
endpoint + trimmings
#19
Changes from 5 commits
95f1a88
a42a75c
f326745
5e2974f
4fef9a0
899a41f
8807989
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,17 +26,32 @@ func initiativeToOAPI(i *pacta.Initiative) (*api.Initiative, error) { | |
}, nil | ||
} | ||
|
||
func userToOAPI(user *pacta.User) (*api.User, error) { | ||
if user == nil { | ||
return nil, errorInternal(fmt.Errorf("userToOAPI: can't convert nil pointer")) | ||
Comment on lines
+29
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I remember you had a reason for using |
||
} | ||
return &api.User{ | ||
Admin: user.Admin, | ||
CanonicalEmail: &user.CanonicalEmail, | ||
EnteredEmail: user.EnteredEmail, | ||
Id: string(user.ID), | ||
Name: user.Name, | ||
PreferredLanguage: api.UserPreferredLanguage(user.PreferredLanguage), | ||
SuperAdmin: user.SuperAdmin, | ||
}, nil | ||
} | ||
|
||
func pactaVersionToOAPI(pv *pacta.PACTAVersion) (*api.PactaVersion, error) { | ||
if pv == nil { | ||
return nil, errorInternal(fmt.Errorf("pactaVersionToOAPI: can't convert nil pointer")) | ||
} | ||
return &api.PactaVersion{ | ||
CreatedAt: pv.CreatedAt, | ||
Description: pv.Description, | ||
Digest: pv.Digest, | ||
Id: string(pv.ID), | ||
Name: pv.Name, | ||
IsDefault: pv.IsDefault, | ||
Digest: pv.Digest, | ||
Description: pv.Description, | ||
CreatedAt: pv.CreatedAt, | ||
Name: pv.Name, | ||
}, nil | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,8 +2,11 @@ package pactasrv | |
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
api "github.com/RMI/pacta/openapi/pacta" | ||
"github.com/RMI/pacta/pacta" | ||
"github.com/go-chi/jwtauth/v5" | ||
) | ||
|
||
// Returns a user by ID | ||
|
@@ -50,3 +53,45 @@ func (s *Server) deleteUser(ctx context.Context, request api.DeleteUserRequestOb | |
// TODO(#12) Implement Authorization | ||
return errorNotImplemented("deleteUser") | ||
} | ||
|
||
// Returns the logged in user | ||
// (GET /user/me) | ||
func (s *Server) FindUserByMe(ctx context.Context, request api.FindUserByMeRequestObject) (api.FindUserByMeResponseObject, error) { | ||
user, err := s.findUserByMe(ctx, request) | ||
if err != nil { | ||
return errToAPIError(err) | ||
} | ||
return api.FindUserByMe200JSONResponse(*user), nil | ||
} | ||
|
||
func (s *Server) findUserByMe(ctx context.Context, request api.FindUserByMeRequestObject) (*api.User, error) { | ||
token, _, err := jwtauth.FromContext(ctx) | ||
if err != nil { | ||
// TODO(grady) upgrade this to the new error handling strategy after #12 | ||
return nil, errorUnauthorized("lookup self", "without authorization token") | ||
} | ||
if token == nil { | ||
return nil, errorUnauthorized("lookup self ", "without authorization token") | ||
} | ||
email, ok := token.PrivateClaims()["emails"] | ||
if !ok { | ||
return nil, errorUnauthorized("lookup self", "without email claim in token") | ||
} | ||
emails, ok := email.([]string) | ||
if !ok || len(emails) == 0 { | ||
return nil, errorInternal(fmt.Errorf("couldn't parse email claim in token")) | ||
} | ||
// TODO(#18) Handle Multiple Emails in the Token Claims gracefully | ||
if len(emails) > 1 { | ||
return nil, errorBadRequest("jwt token", "multiple emails in token") | ||
} | ||
authnID := token.Subject() | ||
if authnID == "" { | ||
return nil, fmt.Errorf("couldn't find authn id in jwt") | ||
} | ||
user, err := s.DB.GetOrCreateUserByAuthn(s.DB.NoTxn(ctx), pacta.AuthnMechanism_EmailAndPass, authnID, emails[0]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would not use a There's a bunch of reasons to do this, but the main one is to have a single location where we create accounts in the DB. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are the other reasons? We can remove the other There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The other reasons are something like:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wait I might be misunderstanding. When does the token from Azure get sent directly to the server? Aren't we always facilitating that exchange through the Frontend? If so wouldn't just having getOrCreateUser make sense at the FE layer too? Agreed on the idea of one method at the DB layer. - I'll make There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The token that would go to a |
||
if err != nil { | ||
return nil, fmt.Errorf("getting user by authn: %w", err) | ||
} | ||
return userToOAPI(user) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { type User } from '@/openapi/generated/pacta' | ||
|
||
export const useSession = () => { | ||
const { pactaClient } = useAPI() | ||
const { error: { handleOAPIError } } = useModal() | ||
|
||
const prefix = 'useSession' | ||
const signedIn = useState<boolean>(`${prefix}.signedIn`, () => true) | ||
|
||
const currentUser = useState<User | undefined>(`${prefix}.currentUser`, () => undefined) | ||
const isAdmin = computed(() => currentUser.value && currentUser.value.admin) | ||
const isSuperAdmin = computed(() => currentUser.value && currentUser.value.superAdmin) | ||
|
||
const resolvers = useState<Array<() => void>>(`${prefix}.resolvers`, () => []) | ||
const loadCurrentUser = (hardRefresh = false): Promise<void> => { | ||
// Return the cached user if we don't explicitly want a new one | ||
if (currentUser.value && !hardRefresh) { | ||
return Promise.resolve() | ||
} | ||
|
||
// We're already loading a user, wait with everyone else | ||
if (resolvers.value.length > 0) { | ||
return new Promise((resolve) => { | ||
resolvers.value.push(resolve) | ||
}) | ||
} | ||
|
||
// We're the first to request a user, kick of the request and hop in line at the front of the queue. | ||
return new Promise((resolve) => { | ||
resolvers.value.push(resolve) | ||
void pactaClient.findUserByMe() | ||
.then((m) => { | ||
console.log(m) | ||
gbdubs marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return m | ||
}) | ||
.then(handleOAPIError) | ||
.then(m => { | ||
currentUser.value = m | ||
|
||
// Let everyone else know we've loaded the user and clear the queue. | ||
resolvers.value.forEach((fn) => { fn() }) | ||
resolvers.value = [] | ||
}) | ||
}) | ||
} | ||
const getMe = async () => { | ||
await loadCurrentUser() | ||
return { | ||
// LoadCurrentUser's return is only undefined as a technicality to support | ||
// the single-lookup behavior above. This cast is safe. | ||
me: currentUser as Ref<User>, | ||
isAdmin, | ||
isSuperAdmin | ||
} | ||
} | ||
const getMaybeMe = async () => { | ||
if (signedIn.value) { | ||
await loadCurrentUser() | ||
} | ||
return { | ||
// Will be a Ref with a value of undefined if the user isn't logged in. | ||
maybeMe: currentUser, | ||
isAdmin, | ||
isSuperAdmin | ||
} | ||
} | ||
gbdubs marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return { | ||
signedIn, | ||
getMe, | ||
getMaybeMe, | ||
currentUser | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hear your point about keeping all the
conv
stuff in thepactasrv
package so you can access these, but my counterpoint would be that determining the 'type' of an error truly is a concern of the server, and this should just return afmt.Errorf(...)
or someconv
-specific error that thesrv
layer then converts into anerrorWhatever
Functionally, it probably doesn't matter, just cleaner abstractions and easier testing