Skip to content

Commit

Permalink
feat: logout redirect to cloud issuer (#3082)
Browse files Browse the repository at this point in the history
* feat: logout redirect to cloud issuer

Signed-off-by: Mark Phelps <[email protected]>

* chore: fix middleware test

Signed-off-by: Mark Phelps <[email protected]>

---------

Signed-off-by: Mark Phelps <[email protected]>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
markphelps and kodiakhq[bot] authored May 14, 2024
1 parent 3732222 commit b50557a
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 52 deletions.
2 changes: 1 addition & 1 deletion cmd/flipt/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
4 changes: 4 additions & 0 deletions internal/server/authn/middleware/grpc/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
21 changes: 11 additions & 10 deletions internal/server/authn/middleware/grpc/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -84,7 +84,7 @@ func TestJWTAuthenticationInterceptor(t *testing.T) {
}
},
expectedJWT: jwt.Expected{
Issuer: "https://flipt.io/",
Issuer: "flipt.io",
Audiences: []string{"flipt"},
Subject: "sunglasses",
},
Expand All @@ -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,
Expand All @@ -111,21 +111,22 @@ func TestJWTAuthenticationInterceptor(t *testing.T) {
}
},
expectedJWT: jwt.Expected{
Issuer: "https://flipt.io/",
Issuer: "flipt.io",
Audiences: []string{"flipt"},
},
expectedMetadata: map[string]string{
"io.flipt.auth.jwt.sub": "sub",
"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,
}
Expand All @@ -136,15 +137,15 @@ func TestJWTAuthenticationInterceptor(t *testing.T) {
}
},
expectedJWT: jwt.Expected{
Issuer: "https://flipt.io/",
Issuer: "flipt.io",
},
expectedErr: ErrUnauthenticated,
},
{
name: "invalid subject",
metadataFunc: func() metadata.MD {
claims := map[string]interface{}{
"iss": "https://flipt.io/",
"iss": "flipt.io",
"iat": nowUnix,
"exp": futureUnix,
"sub": "bar",
Expand All @@ -156,7 +157,7 @@ func TestJWTAuthenticationInterceptor(t *testing.T) {
}
},
expectedJWT: jwt.Expected{
Issuer: "https://flipt.io/",
Issuer: "flipt.io",
Subject: "flipt",
},
expectedErr: ErrUnauthenticated,
Expand All @@ -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",
Expand All @@ -177,7 +178,7 @@ func TestJWTAuthenticationInterceptor(t *testing.T) {
}
},
expectedJWT: jwt.Expected{
Issuer: "https://flipt.io/",
Issuer: "flipt.io",
Audiences: []string{"flipt"},
},
expectedErr: ErrUnauthenticated,
Expand Down
4 changes: 1 addition & 3 deletions ui/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ export default function Header(props: HeaderProps) {
{info && info.updateAvailable && <Notifications info={info} />}

{/* user profile */}
{session && session.self && (
<UserProfile metadata={session.self.metadata} />
)}
{session && session.self && <UserProfile session={session.self} />}
</div>
</div>
</div>
Expand Down
78 changes: 40 additions & 38 deletions ui/src/components/header/UserProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down
1 change: 1 addition & 0 deletions ui/src/types/auth/Github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ export interface IAuthMethodGithubMetadata {
}

export interface IAuthGithubInternal extends IAuth {
method: 'METHOD_GITHUB';
metadata: IAuthMethodGithubMetadata;
}
2 changes: 2 additions & 0 deletions ui/src/types/auth/JWT.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
1 change: 1 addition & 0 deletions ui/src/types/auth/OIDC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ export interface IAuthMethodOIDCMetadata {
}

export interface IAuthOIDCInternal extends IAuth {
method: 'METHOD_OIDC';
metadata: IAuthMethodOIDCMetadata;
}

0 comments on commit b50557a

Please sign in to comment.