From b50557a5ee026d131ee60e59027fc4d50953ba34 Mon Sep 17 00:00:00 2001
From: Mark Phelps <209477+markphelps@users.noreply.github.com>
Date: Tue, 14 May 2024 13:18:38 -0400
Subject: [PATCH] feat: logout redirect to cloud issuer (#3082)
* feat: logout redirect to cloud issuer
Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
* chore: fix middleware test
Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
---------
Signed-off-by: Mark Phelps <209477+markphelps@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
---
cmd/flipt/cloud.go | 2 +-
.../authn/middleware/grpc/middleware.go | 4 +
.../authn/middleware/grpc/middleware_test.go | 21 ++---
ui/src/components/Header.tsx | 4 +-
ui/src/components/header/UserProfile.tsx | 78 ++++++++++---------
ui/src/types/auth/Github.ts | 1 +
ui/src/types/auth/JWT.ts | 2 +
ui/src/types/auth/OIDC.ts | 1 +
8 files changed, 61 insertions(+), 52 deletions(-)
diff --git a/cmd/flipt/cloud.go b/cmd/flipt/cloud.go
index fa0591b6a8..99dba9758e 100644
--- a/cmd/flipt/cloud.go
+++ b/cmd/flipt/cloud.go
@@ -151,7 +151,7 @@ func (c *cloudCommand) login(cmd *cobra.Command, args []string) error {
return fmt.Errorf("waiting for token: %w", err)
}
- if err := flow.Close(); err != nil {
+ if err := flow.Close(); err != nil && !errors.Is(err, net.ErrClosed) {
return fmt.Errorf("closing flow: %w", err)
}
diff --git a/internal/server/authn/middleware/grpc/middleware.go b/internal/server/authn/middleware/grpc/middleware.go
index 276c4eea89..1ba9263789 100644
--- a/internal/server/authn/middleware/grpc/middleware.go
+++ b/internal/server/authn/middleware/grpc/middleware.go
@@ -212,6 +212,10 @@ func JWTAuthenticationInterceptor(logger *zap.Logger, validator jwt.Validator, e
}
}
+ if iss, ok := jwtClaims["iss"].(string); ok {
+ metadata["io.flipt.auth.jwt.issuer"] = iss
+ }
+
auth := &authrpc.Authentication{
Method: authrpc.Method_METHOD_JWT,
Metadata: metadata,
diff --git a/internal/server/authn/middleware/grpc/middleware_test.go b/internal/server/authn/middleware/grpc/middleware_test.go
index c0997a061b..feb60756c2 100644
--- a/internal/server/authn/middleware/grpc/middleware_test.go
+++ b/internal/server/authn/middleware/grpc/middleware_test.go
@@ -71,7 +71,7 @@ func TestJWTAuthenticationInterceptor(t *testing.T) {
name: "successful authentication",
metadataFunc: func() metadata.MD {
claims := map[string]interface{}{
- "iss": "https://flipt.io/",
+ "iss": "flipt.io",
"aud": "flipt",
"sub": "sunglasses",
"iat": nowUnix,
@@ -84,7 +84,7 @@ func TestJWTAuthenticationInterceptor(t *testing.T) {
}
},
expectedJWT: jwt.Expected{
- Issuer: "https://flipt.io/",
+ Issuer: "flipt.io",
Audiences: []string{"flipt"},
Subject: "sunglasses",
},
@@ -93,7 +93,7 @@ func TestJWTAuthenticationInterceptor(t *testing.T) {
name: "successful authentication (with custom user claims)",
metadataFunc: func() metadata.MD {
claims := map[string]interface{}{
- "iss": "https://flipt.io/",
+ "iss": "flipt.io",
"aud": "flipt",
"iat": nowUnix,
"exp": futureUnix,
@@ -111,7 +111,7 @@ func TestJWTAuthenticationInterceptor(t *testing.T) {
}
},
expectedJWT: jwt.Expected{
- Issuer: "https://flipt.io/",
+ Issuer: "flipt.io",
Audiences: []string{"flipt"},
},
expectedMetadata: map[string]string{
@@ -119,13 +119,14 @@ func TestJWTAuthenticationInterceptor(t *testing.T) {
"io.flipt.auth.jwt.email": "email",
"io.flipt.auth.jwt.picture": "image",
"io.flipt.auth.jwt.name": "name",
+ "io.flipt.auth.jwt.issuer": "flipt.io",
},
},
{
name: "invalid issuer",
metadataFunc: func() metadata.MD {
claims := map[string]interface{}{
- "iss": "https://foo.com/",
+ "iss": "foo.com",
"iat": nowUnix,
"exp": futureUnix,
}
@@ -136,7 +137,7 @@ func TestJWTAuthenticationInterceptor(t *testing.T) {
}
},
expectedJWT: jwt.Expected{
- Issuer: "https://flipt.io/",
+ Issuer: "flipt.io",
},
expectedErr: ErrUnauthenticated,
},
@@ -144,7 +145,7 @@ func TestJWTAuthenticationInterceptor(t *testing.T) {
name: "invalid subject",
metadataFunc: func() metadata.MD {
claims := map[string]interface{}{
- "iss": "https://flipt.io/",
+ "iss": "flipt.io",
"iat": nowUnix,
"exp": futureUnix,
"sub": "bar",
@@ -156,7 +157,7 @@ func TestJWTAuthenticationInterceptor(t *testing.T) {
}
},
expectedJWT: jwt.Expected{
- Issuer: "https://flipt.io/",
+ Issuer: "flipt.io",
Subject: "flipt",
},
expectedErr: ErrUnauthenticated,
@@ -165,7 +166,7 @@ func TestJWTAuthenticationInterceptor(t *testing.T) {
name: "invalid audience",
metadataFunc: func() metadata.MD {
claims := map[string]interface{}{
- "iss": "https://flipt.io/",
+ "iss": "flipt.io",
"iat": nowUnix,
"exp": futureUnix,
"aud": "bar",
@@ -177,7 +178,7 @@ func TestJWTAuthenticationInterceptor(t *testing.T) {
}
},
expectedJWT: jwt.Expected{
- Issuer: "https://flipt.io/",
+ Issuer: "flipt.io",
Audiences: []string{"flipt"},
},
expectedErr: ErrUnauthenticated,
diff --git a/ui/src/components/Header.tsx b/ui/src/components/Header.tsx
index 2922997bdf..dfd0d1c9ad 100644
--- a/ui/src/components/Header.tsx
+++ b/ui/src/components/Header.tsx
@@ -39,9 +39,7 @@ export default function Header(props: HeaderProps) {
{info && info.updateAvailable && }
{/* user profile */}
- {session && session.self && (
-
- )}
+ {session && session.self && }
diff --git a/ui/src/components/header/UserProfile.tsx b/ui/src/components/header/UserProfile.tsx
index a12a3c5980..917f826f0e 100644
--- a/ui/src/components/header/UserProfile.tsx
+++ b/ui/src/components/header/UserProfile.tsx
@@ -4,63 +4,65 @@ import { Fragment } from 'react';
import { expireAuthSelf } from '~/data/api';
import { useError } from '~/data/hooks/error';
import { useSession } from '~/data/hooks/session';
-import { IAuthMethodGithubMetadata } from '~/types/auth/Github';
-import { IAuthMethodJWTMetadata } from '~/types/auth/JWT';
-import { IAuthMethodOIDCMetadata } from '~/types/auth/OIDC';
+import { IAuthGithubInternal } from '~/types/auth/Github';
+import { IAuthJWTInternal } from '~/types/auth/JWT';
+import { IAuthOIDCInternal } from '~/types/auth/OIDC';
import { cls } from '~/utils/helpers';
type UserProfileProps = {
- metadata?:
- | IAuthMethodOIDCMetadata
- | IAuthMethodGithubMetadata
- | IAuthMethodJWTMetadata;
+ session?: IAuthOIDCInternal | IAuthGithubInternal | IAuthJWTInternal;
};
export default function UserProfile(props: UserProfileProps) {
- const { metadata } = props;
-
+ const { session } = props;
const { setError } = useError();
const { clearSession } = useSession();
let name: string | undefined;
let login: string | undefined;
let imgURL: string | undefined;
+ let logoutURL = '/';
- if (metadata) {
- // TODO: dry this up
- if ('io.flipt.auth.github.name' in metadata) {
- name = metadata['io.flipt.auth.github.name'] ?? 'User';
- if (metadata['io.flipt.auth.github.picture']) {
- imgURL = metadata['io.flipt.auth.github.picture'];
- }
- if (metadata['io.flipt.auth.github.preferred_username']) {
- login = metadata['io.flipt.auth.github.preferred_username'];
- }
- } else if ('io.flipt.auth.oidc.name' in metadata) {
- name = metadata['io.flipt.auth.oidc.name'] ?? 'User';
- if (metadata['io.flipt.auth.oidc.picture']) {
- imgURL = metadata['io.flipt.auth.oidc.picture'];
+ if (session) {
+ const authMethods = ['github', 'oidc', 'jwt'];
+ const authMethod = authMethods.find(
+ (method) => `METHOD_${method.toLocaleUpperCase()}` === session.method
+ );
+
+ if (authMethod) {
+ const metadata = session.metadata;
+
+ const authMethodNameKey = `io.flipt.auth.${authMethod}.name`;
+ name = metadata[authMethodNameKey as keyof typeof metadata] ?? 'User';
+
+ const authMethodPictureKey = `io.flipt.auth.${authMethod}.picture`;
+ if (metadata[authMethodPictureKey as keyof typeof metadata]) {
+ imgURL = metadata[authMethodPictureKey as keyof typeof metadata];
}
- if (metadata['io.flipt.auth.oidc.preferred_username']) {
- login = metadata['io.flipt.auth.oidc.preferred_username'];
+
+ const authMethodPreferredUsernameKey = `io.flipt.auth.${authMethod}.preferred_username`;
+ if (metadata[authMethodPreferredUsernameKey as keyof typeof metadata]) {
+ login =
+ metadata[authMethodPreferredUsernameKey as keyof typeof metadata];
}
- } else if ('io.flipt.auth.jwt.name' in metadata) {
- name = metadata['io.flipt.auth.jwt.name'] ?? 'User';
- if (metadata['io.flipt.auth.jwt.picture']) {
- imgURL = metadata['io.flipt.auth.jwt.picture'];
+
+ const authMethodIssuerKey = `io.flipt.auth.${authMethod}.issuer`;
+ if (metadata[authMethodIssuerKey as keyof typeof metadata]) {
+ const logoutURI =
+ metadata[authMethodIssuerKey as keyof typeof metadata];
+ logoutURL = `//${logoutURI}`;
}
}
}
- const logout = async () => {
- expireAuthSelf()
- .then(() => {
- clearSession();
- window.location.href = '/';
- })
- .catch((err) => {
- setError(err);
- });
+ const logout = () => {
+ try {
+ expireAuthSelf();
+ clearSession();
+ window.location.href = logoutURL;
+ } catch (err) {
+ setError(err);
+ }
};
return (
diff --git a/ui/src/types/auth/Github.ts b/ui/src/types/auth/Github.ts
index 810eaa6ba2..d8152528b9 100644
--- a/ui/src/types/auth/Github.ts
+++ b/ui/src/types/auth/Github.ts
@@ -16,5 +16,6 @@ export interface IAuthMethodGithubMetadata {
}
export interface IAuthGithubInternal extends IAuth {
+ method: 'METHOD_GITHUB';
metadata: IAuthMethodGithubMetadata;
}
diff --git a/ui/src/types/auth/JWT.ts b/ui/src/types/auth/JWT.ts
index db6a50d142..1d2c694c5a 100644
--- a/ui/src/types/auth/JWT.ts
+++ b/ui/src/types/auth/JWT.ts
@@ -12,8 +12,10 @@ export interface IAuthMethodJWTMetadata {
'io.flipt.auth.jwt.email'?: string;
'io.flipt.auth.jwt.name'?: string;
'io.flipt.auth.jwt.picture'?: string;
+ 'io.flipt.auth.jwt.issuer'?: string;
}
export interface IAuthJWTInternal extends IAuth {
+ method: 'METHOD_JWT';
metadata: IAuthMethodJWTMetadata;
}
diff --git a/ui/src/types/auth/OIDC.ts b/ui/src/types/auth/OIDC.ts
index ee490495c4..4e7c31384e 100644
--- a/ui/src/types/auth/OIDC.ts
+++ b/ui/src/types/auth/OIDC.ts
@@ -22,5 +22,6 @@ export interface IAuthMethodOIDCMetadata {
}
export interface IAuthOIDCInternal extends IAuth {
+ method: 'METHOD_OIDC';
metadata: IAuthMethodOIDCMetadata;
}