-
Notifications
You must be signed in to change notification settings - Fork 996
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
feat(rsc-auth): Implement getRoles function in auth mw & update default ServerAuthState #10656
Conversation
Update default serverAuthState to contain roles Make cookieHeader a required attribute
…-role-authmw * 'main' of github.com:redwoodjs/redwood: feat(auth-middleware): Return a Tuple with Route pattern configuration when creating dbAuth middleware (redwoodjs#10642)
f68acec
to
5f47249
Compare
…-role-authmw * 'main' of github.com:redwoodjs/redwood: fix(dbAuthMw): Update and fix logic related to dbAuth "verbs" and decryptionErrors (redwoodjs#10668) RSC: routes-auto-loader is not used for SSR anymore (redwoodjs#10672) chore(crwa): Remove unused jest dev dependency (redwoodjs#10673) RSC: rscBuildEntriesFile: Only ServerEntry and Routes needed for serverEntries (redwoodjs#10671) RSC: clientSsr: getServerEntryComponent() (redwoodjs#10670) RSC: worker: getFunctionComponent -> getRoutesComponent (redwoodjs#10669) RSC: kitchen-sink: Make the ReadFileServerCell output take up less space (redwoodjs#10667) RSC: Remove commented code related to prefixToRemove transform() (redwoodjs#10666) RSC Client Router (redwoodjs#10557) RSC: Add 'use client' to remaining client cells in kitchen-sink (redwoodjs#10665) RSC: vite auto-loader: Spell out 'path' and other chores (redwoodjs#10662) fix(cli): Handle case for no arguments for verbose baremetal deploy (redwoodjs#10663) RSC: kitchen-sink: Make it more clear where layout ends and main content starts (redwoodjs#10661) RSC: Make the kitchen-sink smoke-test more robust/resilient (redwoodjs#10660) RSC: Source format of EmptyUsersCell in kitchen-sink (redwoodjs#10658) RSC: Add 'use client' to all client cells in kitchen-sink (redwoodjs#10659) chore(__fixtures__): Follow-up: Make test projects match newer CRWA template (redwoodjs#10657) feat: Reworks RSC server entries and route manifest building to derive from routes and include if route info related to authentication (redwoodjs#10572) chore(__fixtures__): Make test projects match newer CRWA template (redwoodjs#10655)
…nto feat/extract-role-authmw * 'feat/extract-role-authmw' of github.com:dac09/redwood:
✋ HODL merge till we've gone through Server Router changes. |
@@ -68,6 +70,8 @@ const createSupabaseAuthMiddleware = ({ | |||
isAuthenticated: !!currentUser, | |||
hasError: false, | |||
userMetadata: userMetadata || currentUser, | |||
cookieHeader, | |||
roles: extractRoles ? extractRoles(decoded) : [], |
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 am trying to think of a case where the decoded data from the authDecoder is needed to get roles -- and not just the currentUser.
I guess one doesn't have to set roles inside getCurrentUser
but typically for the auth providers we do.
Though this does give some flexibility to define roles outside the currentUser .... which might make ABAC permission easier in the future. Ie - check a permission file/config or lookup db --- for if the username starts with admin , then then get the admin role.
Think will have to use it a bit and get the feel for it, but makes sense and can see ow it will work.
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 think for example, if you add roles in supabase user_metadata, it doesn't come through as currentUser.roles!
Hey DT, so I lookedup up types in the auth providers, and here's my notes on it. It just validates that we definitely need this getRoles function, but also that we could potentially have a default implementation for each auth provider (if no roles, just return Supabase userRoles in user.role, but I don't think that's the one we want to use. That's the role supabase uses I believe. In the examples I looked through with you it was in export interface User {
id: string
app_metadata: UserAppMetadata
user_metadata: UserMetadata
aud: string
confirmation_sent_at?: string
recovery_sent_at?: string
email_change_sent_at?: string
new_email?: string
new_phone?: string
invited_at?: string
action_link?: string
email?: string
phone?: string
created_at: string
confirmed_at?: string
email_confirmed_at?: string
phone_confirmed_at?: string
last_sign_in_at?: string
role?: string
updated_at?: string
identities?: UserIdentity[]
is_anonymous?: boolean
factors?: Factor[]
} Firebase userDoesn't have roles built in. Possibly can be added to UserMetadata. export declare interface User extends UserInfo {
readonly emailVerified: boolean;
readonly isAnonymous: boolean;
readonly metadata: UserMetadata;
readonly providerData: UserInfo[];
readonly refreshToken: string;
readonly tenantId: string | null;
delete(): Promise<void>;
getIdToken(forceRefresh?: boolean): Promise<string>;
getIdTokenResult(forceRefresh?: boolean): Promise<IdTokenResult>;
reload(): Promise<void>;
toJSON(): object;
} Netlify userRoles available in user.app_metadata.roles export interface User {
api: {
_sameOrigin?: boolean | undefined;
apiURL: string;
defaultHeaders: {
[header: string]: string | string[] | undefined;
};
};
app_metadata: {
provider: string;
roles: string[];
};
aud: string;
audience?: any;
confirmed_at: string;
created_at: string;
updated_at: string;
invited_at: string;
recovery_sent_at: string;
email: string;
id: string;
role: string;
token?: Token | undefined;
url: string;
user_metadata: {
avatar_url?: string;
full_name?: string;
} | null;
} Clerk userCan't see roles directly in the user object, but likely to be in user.publicMetadata ? export interface UserResource extends ClerkResource {
id: string;
externalId: string | null;
primaryEmailAddressId: string | null;
primaryEmailAddress: EmailAddressResource | null;
primaryPhoneNumberId: string | null;
primaryPhoneNumber: PhoneNumberResource | null;
primaryWeb3WalletId: string | null;
primaryWeb3Wallet: Web3WalletResource | null;
username: string | null;
fullName: string | null;
firstName: string | null;
lastName: string | null;
profileImageUrl: string;
imageUrl: string;
hasImage: boolean;
emailAddresses: EmailAddressResource[];
phoneNumbers: PhoneNumberResource[];
web3Wallets: Web3WalletResource[];
externalAccounts: ExternalAccountResource[];
samlAccounts: SamlAccountResource[];
organizationMemberships: OrganizationMembershipResource[];
passwordEnabled: boolean;
totpEnabled: boolean;
backupCodeEnabled: boolean;
twoFactorEnabled: boolean;
publicMetadata: UserPublicMetadata;
unsafeMetadata: UserUnsafeMetadata;
lastSignInAt: Date | null;
createOrganizationEnabled: boolean;
deleteSelfEnabled: boolean;
updatedAt: Date | null;
createdAt: Date | null;
update: (params: UpdateUserParams) => Promise<UserResource>;
delete: () => Promise<void>;
updatePassword: (params: UpdateUserPasswordParams) => Promise<UserResource>;
removePassword: (params: RemoveUserPasswordParams) => Promise<UserResource>;
createEmailAddress: (params: CreateEmailAddressParams) => Promise<EmailAddressResource>;
createPhoneNumber: (params: CreatePhoneNumberParams) => Promise<PhoneNumberResource>;
createWeb3Wallet: (params: CreateWeb3WalletParams) => Promise<Web3WalletResource>;
isPrimaryIdentification: (ident: EmailAddressResource | PhoneNumberResource | Web3WalletResource) => boolean;
getSessions: () => Promise<SessionWithActivitiesResource[]>;
setProfileImage: (params: SetProfileImageParams) => Promise<ImageResource>;
createExternalAccount: (params: CreateExternalAccountParams) => Promise<ExternalAccountResource>;
getOrganizationMemberships: GetOrganizationMemberships;
getOrganizationInvitations: (params?: GetUserOrganizationInvitationsParams) => Promise<ClerkPaginatedResponse<UserOrganizationInvitationResource>>;
getOrganizationSuggestions: (params?: GetUserOrganizationSuggestionsParams) => Promise<ClerkPaginatedResponse<OrganizationSuggestionResource>>;
leaveOrganization: (organizationId: string) => Promise<DeletedObjectResource>;
createTOTP: () => Promise<TOTPResource>;
verifyTOTP: (params: VerifyTOTPParams) => Promise<TOTPResource>;
disableTOTP: () => Promise<DeletedObjectResource>;
createBackupCode: () => Promise<BackupCodeResource>;
get verifiedExternalAccounts(): ExternalAccountResource[];
get unverifiedExternalAccounts(): ExternalAccountResource[];
get hasVerifiedEmailAddress(): boolean;
get hasVerifiedPhoneNumber(): boolean;
} Auth0No roles, but possibly comes through in scopes in the JWT?
dbAuth UserUp to the user! But in our test-project fixture this is the Prisma schema:
|
packages/auth-providers/supabase/middleware/src/__tests__/defaultGetRoles.test.ts
Show resolved
Hide resolved
@dac09 I tested type SupabaseAppMetadata = {
provider: string
providers: string[]
roles: string[]
}
const supabaseAuthMiddleware = initSupabaseMiddleware({
getCurrentUser,
getRoles: ({ app_metadata }: { app_metadata: SupabaseAppMetadata }) => {
console.log('>>>> in getRoles', app_metadata.roles)
return app_metadata.roles
},
})
return [supabaseAuthMiddleware]
} Typically, roles in Supabase will be stored in app_metadata (data the app but the user cannot change) and preferences (data the user may change) in user_metadata: update AUTH.users
set raw_app_meta_data = raw_app_meta_data || '{"roles": "admin"}',
raw_user_meta_data = raw_user_meta_data || '{"favoriteColor": "yellow"}';
select * from auth.users; Here the role in app_metadata is admin and can confirm this role is enforced: <Set wrap={BlogLayout}>
<Route path="/waterfall/{id:Int}" page={WaterfallPage} prerender name="waterfall" />
<PrivateSet unauthenticated="login" roles={['admin']}>
<Route path="/profile" page={ProfilePage} name="profile" />
</PrivateSet>
<Route path="/blog-post/{id:Int}" page={BlogPostPage} name="blogPost" prerender />
<Route path="/contact" page={ContactUsPage} name="contactUs" />
<PrivateSet unauthenticated="login" roles={['superuser']}>
<Route path="/about" page={AboutPage} name="about" prerender />
</PrivateSet>
<Route path="/" page={HomePage} name="home" prerender />
<Route notfound page={NotFoundPage} prerender />
</Set>
|
Also, the dev can decided to keep the
and then in type SupabaseAppMetadata = {
provider: string
providers: string[]
roles: string[]
}
export const getRoles = ({
app_metadata,
}: {
app_metadata: SupabaseAppMetadata
}) => {
console.log('>>>> in getRoles', app_metadata.roles)
return app_metadata.roles
} which is nice. |
@dac09 Approved. Good to merge. I tested with string and array roles in a custom get roles. Working well. |
clear()
function to remove auth state - just syntax sugarOutstanding
Example usage