From 7c3888068d33f71c687d7cc256a37fbb7511eff6 Mon Sep 17 00:00:00 2001 From: George Date: Mon, 30 Oct 2023 17:29:09 +0000 Subject: [PATCH 1/4] refactor(ui): redux all flag and variant state management (#2301) * refactor(ui/flags): use redux for current flag state * refactor(ui/flags): use redux for delete flag * refactor(ui/flags): use redux for copy flag * refactor(ui/flags): use redux for create and update * fix(ui/flags): define FlagForm flag prop as optional * refactor(ui/flags): use redux for delete variant * fix(ui/flags): restore error setting flag dispatch fails * refactor(ui/flags): use a selector with args for selectFlag * refactor(ui/flags): use redux for create/update variant * chore(ui): fixup lint errors * chore: run prettier * chore(ui): use project rooted import instead of relative path --------- Co-authored-by: Mark Phelps <209477+markphelps@users.noreply.github.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- ui/src/app/flags/Flag.tsx | 53 +++-- ui/src/app/flags/flagsSlice.ts | 250 ++++++++++++++++++++++++ ui/src/app/flags/variants/Variants.tsx | 23 ++- ui/src/components/flags/FlagForm.tsx | 29 +-- ui/src/components/flags/VariantForm.tsx | 22 ++- ui/src/store.ts | 7 + 6 files changed, 337 insertions(+), 47 deletions(-) create mode 100644 ui/src/app/flags/flagsSlice.ts diff --git a/ui/src/app/flags/Flag.tsx b/ui/src/app/flags/Flag.tsx index fb6cd14adc..2e2d561968 100644 --- a/ui/src/app/flags/Flag.tsx +++ b/ui/src/app/flags/Flag.tsx @@ -19,56 +19,61 @@ import Modal from '~/components/Modal'; import MoreInfo from '~/components/MoreInfo'; import CopyToNamespacePanel from '~/components/panels/CopyToNamespacePanel'; import DeletePanel from '~/components/panels/DeletePanel'; -import { copyFlag, deleteFlag, getFlag } from '~/data/api'; import { useError } from '~/data/hooks/error'; +import { useAppDispatch } from '~/data/hooks/store'; import { useSuccess } from '~/data/hooks/success'; import { useTimezone } from '~/data/hooks/timezone'; -import { FlagType, IFlag } from '~/types/Flag'; +import { RootState } from '~/store'; +import { FlagType } from '~/types/Flag'; import { classNames } from '~/utils/helpers'; +import { + copyFlagAsync, + deleteFlagAsync, + fetchFlagAsync, + selectFlag +} from './flagsSlice'; import Rollouts from './rollouts/Rollouts'; export default function Flag() { let { flagKey } = useParams(); const { inTimezone } = useTimezone(); - const [flag, setFlag] = useState(null); - const [flagVersion, setFlagVersion] = useState(0); - const { setError, clearError } = useError(); const { setSuccess } = useSuccess(); const navigate = useNavigate(); + const dispatch = useAppDispatch(); const namespaces = useSelector(selectNamespaces); const namespace = useSelector(selectCurrentNamespace); const readOnly = useSelector(selectReadonly); + const flag = useSelector((state: RootState) => + selectFlag(state, namespace.key, flagKey || '') + ); + const [showDeleteFlagModal, setShowDeleteFlagModal] = useState(false); const [showCopyFlagModal, setShowCopyFlagModal] = useState(false); - const incrementFlagVersion = () => { - setFlagVersion(flagVersion + 1); - }; - const tabs = [ { name: 'Variants', to: '' }, { name: 'Rules', to: 'rules' } ]; useEffect(() => { - if (!flagKey) return; + if (!namespace.key || !flagKey) return; - getFlag(namespace.key, flagKey) - .then((flag: IFlag) => { - setFlag(flag); + dispatch(fetchFlagAsync({ namespaceKey: namespace.key, key: flagKey })) + .unwrap() + .then(() => { clearError(); }) .catch((err) => { setError(err); }); - }, [flagVersion, flagKey, namespace.key, clearError, setError]); + }, [flagKey, namespace.key, clearError, setError]); - if (!flag) return ; + if (!flag || flag.key != flagKey) return ; return ( <> @@ -84,7 +89,11 @@ export default function Flag() { } panelType="Flag" setOpen={setShowDeleteFlagModal} - handleDelete={() => deleteFlag(namespace.key, flag.key)} + handleDelete={() => + dispatch( + deleteFlagAsync({ namespaceKey: namespace.key, key: flag.key }) + ) + } onSuccess={() => { navigate(`/namespaces/${namespace.key}/flags`); }} @@ -104,9 +113,11 @@ export default function Flag() { panelType="Flag" setOpen={setShowCopyFlagModal} handleCopy={(namespaceKey: string) => - copyFlag( - { namespaceKey: namespace.key, key: flag.key }, - { namespaceKey: namespaceKey, key: flag.key } + dispatch( + copyFlagAsync({ + from: { namespaceKey: namespace.key, key: flag.key }, + to: { namespaceKey: namespaceKey, key: flag.key } + }) ) } onSuccess={() => { @@ -182,7 +193,7 @@ export default function Flag() {
- +
@@ -212,7 +223,7 @@ export default function Flag() { - + )} {flag.type === FlagType.BOOLEAN && } diff --git a/ui/src/app/flags/flagsSlice.ts b/ui/src/app/flags/flagsSlice.ts new file mode 100644 index 0000000000..94a582fe8d --- /dev/null +++ b/ui/src/app/flags/flagsSlice.ts @@ -0,0 +1,250 @@ +/* eslint-disable @typescript-eslint/no-use-before-define */ +import { + ActionReducerMapBuilder, + createAsyncThunk, + createSelector, + createSlice, + SerializedError +} from '@reduxjs/toolkit'; +import { + copyFlag, + createFlag, + createVariant, + deleteFlag, + deleteVariant, + getFlag, + updateFlag, + updateVariant +} from '~/data/api'; +import { RootState } from '~/store'; +import { IFlag, IFlagBase } from '~/types/Flag'; +import { LoadingStatus } from '~/types/Meta'; +import { IVariant, IVariantBase } from '~/types/Variant'; + +interface IFlagState { + status: LoadingStatus; + flags: { [key: string]: { [key: string]: IFlag } }; + error: string | undefined; +} + +const initialState: IFlagState = { + status: LoadingStatus.IDLE, + flags: {}, + error: undefined +}; + +export const flagsSlice = createSlice({ + name: 'flags', + initialState, + reducers: {}, + extraReducers(builder) { + [flagReducers, variantReducers].map((reducer) => reducer(builder)); + } +}); + +const flagReducers = (builder: ActionReducerMapBuilder) => { + builder + .addCase(fetchFlagAsync.pending, setLoading) + .addCase(fetchFlagAsync.fulfilled, setFlag) + .addCase(fetchFlagAsync.rejected, setError) + .addCase(createFlagAsync.pending, setLoading) + .addCase(createFlagAsync.fulfilled, setFlag) + .addCase(createFlagAsync.rejected, setError) + .addCase(updateFlagAsync.pending, setLoading) + .addCase(updateFlagAsync.fulfilled, setFlag) + .addCase(updateFlagAsync.rejected, setError) + .addCase(deleteFlagAsync.fulfilled, (state, action) => { + state.status = LoadingStatus.SUCCEEDED; + const flags = state.flags[action.meta.arg.namespaceKey]; + if (flags) { + delete flags[action.meta.arg.key]; + } + }) + .addCase(deleteFlagAsync.rejected, setError); +}; + +const variantReducers = (builder: ActionReducerMapBuilder) => { + builder + .addCase(createVariantAsync.pending, setLoading) + .addCase( + createVariantAsync.fulfilled, + ( + state: IFlagState, + action: { payload: IVariant; meta: { arg: VariantValues } } + ) => { + state.status = LoadingStatus.SUCCEEDED; + const flags = state.flags[action.meta.arg.namespaceKey]; + if (flags) { + flags[action.meta.arg.flagKey].variants?.push(action.payload); + } + } + ) + .addCase(createVariantAsync.rejected, setError) + .addCase(updateVariantAsync.pending, setLoading) + .addCase( + updateVariantAsync.fulfilled, + ( + state: IFlagState, + action: { payload: IVariant; meta: { arg: VariantValues } } + ) => { + state.status = LoadingStatus.SUCCEEDED; + const flags = state.flags[action.meta.arg.namespaceKey]; + if (flags) { + const variants = flags[action.meta.arg.flagKey].variants || []; + const idx = variants.findIndex( + (v) => v.key == action.meta.arg.values.key + ); + if (idx >= 0) { + variants[idx] = action.payload; + } + flags[action.meta.arg.flagKey].variants = variants; + } + } + ) + .addCase(updateVariantAsync.rejected, setError) + .addCase(deleteVariantAsync.fulfilled, (state, action) => { + state.status = LoadingStatus.SUCCEEDED; + const flags = state.flags[action.meta.arg.namespaceKey]; + if (flags) { + const variants = flags[action.meta.arg.flagKey].variants || []; + flags[action.meta.arg.flagKey].variants = variants.filter( + (variant) => variant.id !== action.meta.arg.variantId + ); + } + }) + .addCase(deleteVariantAsync.rejected, setError); +}; + +const setLoading = (state: IFlagState, _action: any) => { + state.status = LoadingStatus.LOADING; +}; + +const setError = ( + state: IFlagState, + action: { payload: any; error: SerializedError } +) => { + state.status = LoadingStatus.FAILED; + state.error = action.error.message; +}; + +const setFlag = ( + state: IFlagState, + action: { payload: IFlag; meta: { arg: { namespaceKey: string } } } +) => { + state.status = LoadingStatus.SUCCEEDED; + const [flag, namespaceKey] = [action.payload, action.meta.arg.namespaceKey]; + if (state.flags[namespaceKey] === undefined) { + state.flags[namespaceKey] = {}; + } + state.flags[namespaceKey][flag.key] = flag; +}; + +export const selectFlag = createSelector( + [ + (state: RootState, namespaceKey: string, key: string) => { + const flags = state.flags.flags[namespaceKey]; + if (flags) { + return flags[key] || ({} as IFlag); + } + + return {} as IFlag; + } + ], + (flag) => flag +); + +interface FlagIdentifier { + namespaceKey: string; + key: string; +} + +export const fetchFlagAsync = createAsyncThunk( + 'flags/fetchFlag', + async (payload: FlagIdentifier) => { + const { namespaceKey, key } = payload; + const response = await getFlag(namespaceKey, key); + return response; + } +); + +export const copyFlagAsync = createAsyncThunk( + 'flags/copyFlag', + async (payload: { from: FlagIdentifier; to: FlagIdentifier }) => { + const { from, to } = payload; + const response = await copyFlag(from, to); + return response; + } +); + +export const createFlagAsync = createAsyncThunk( + 'flags/createFlag', + async (payload: { namespaceKey: string; values: IFlagBase }) => { + const { namespaceKey, values } = payload; + const response = await createFlag(namespaceKey, values); + return response; + } +); + +export const updateFlagAsync = createAsyncThunk( + 'flags/updateFlag', + async (payload: { namespaceKey: string; key: string; values: IFlagBase }) => { + const { namespaceKey, key, values } = payload; + const response = await updateFlag(namespaceKey, key, values); + return response; + } +); + +export const deleteFlagAsync = createAsyncThunk( + 'flags/deleteFlag', + async (payload: FlagIdentifier) => { + const { namespaceKey, key } = payload; + const response = await deleteFlag(namespaceKey, key); + return response; + } +); + +interface VariantIdentifier { + namespaceKey: string; + flagKey: string; + variantId: string; +} + +export const deleteVariantAsync = createAsyncThunk( + 'flags/deleteVariant', + async (payload: VariantIdentifier) => { + const { namespaceKey, flagKey, variantId } = payload; + const response = await deleteVariant(namespaceKey, flagKey, variantId); + return response; + } +); + +interface VariantValues { + namespaceKey: string; + flagKey: string; + values: IVariantBase; +} + +export const createVariantAsync = createAsyncThunk( + 'flags/createVariant', + async (payload: VariantValues) => { + const { namespaceKey, flagKey, values } = payload; + const response = await createVariant(namespaceKey, flagKey, values); + return response; + } +); + +export const updateVariantAsync = createAsyncThunk( + 'flags/updateVariant', + async (payload: VariantIdentifier & VariantValues) => { + const { namespaceKey, flagKey, variantId, values } = payload; + const response = await updateVariant( + namespaceKey, + flagKey, + variantId, + values + ); + return response; + } +); + +export default flagsSlice.reducer; diff --git a/ui/src/app/flags/variants/Variants.tsx b/ui/src/app/flags/variants/Variants.tsx index e12e0a7a7e..8243a63fc3 100644 --- a/ui/src/app/flags/variants/Variants.tsx +++ b/ui/src/app/flags/variants/Variants.tsx @@ -10,17 +10,17 @@ import Button from '~/components/forms/buttons/Button'; import Modal from '~/components/Modal'; import DeletePanel from '~/components/panels/DeletePanel'; import Slideover from '~/components/Slideover'; -import { deleteVariant } from '~/data/api'; +import { useAppDispatch } from '~/data/hooks/store'; import { IFlag } from '~/types/Flag'; import { IVariant } from '~/types/Variant'; +import { deleteVariantAsync } from '~/app/flags/flagsSlice'; type VariantsProps = { flag: IFlag; - onFlagChange: () => void; }; export default function Variants() { - const { flag, onFlagChange } = useOutletContext(); + const { flag } = useOutletContext(); const [showVariantForm, setShowVariantForm] = useState(false); const [editingVariant, setEditingVariant] = useState(null); @@ -28,6 +28,8 @@ export default function Variants() { useState(false); const [deletingVariant, setDeletingVariant] = useState(null); + const dispatch = useAppDispatch(); + const variantFormRef = useRef(null); const namespace = useSelector(selectCurrentNamespace); @@ -48,7 +50,6 @@ export default function Variants() { setOpen={setShowVariantForm} onSuccess={() => { setShowVariantForm(false); - onFlagChange(); }} /> @@ -67,13 +68,15 @@ export default function Variants() { } panelType="Variant" setOpen={setShowDeleteVariantModal} - handleDelete={ - () => - deleteVariant(namespace.key, flag.key, deletingVariant?.id ?? '') // TODO: Determine impact of blank ID param + handleDelete={() => + dispatch( + deleteVariantAsync({ + namespaceKey: namespace.key, + flagKey: flag.key, + variantId: deletingVariant?.id ?? '' + }) + ).unwrap() } - onSuccess={() => { - onFlagChange(); - }} /> diff --git a/ui/src/components/flags/FlagForm.tsx b/ui/src/components/flags/FlagForm.tsx index 9583c09346..9e0e867f37 100644 --- a/ui/src/components/flags/FlagForm.tsx +++ b/ui/src/components/flags/FlagForm.tsx @@ -4,24 +4,20 @@ import { useState } from 'react'; import { useSelector } from 'react-redux'; import { useNavigate } from 'react-router-dom'; import * as Yup from 'yup'; +import { createFlagAsync, updateFlagAsync } from '~/app/flags/flagsSlice'; import { selectReadonly } from '~/app/meta/metaSlice'; import { selectCurrentNamespace } from '~/app/namespaces/namespacesSlice'; import Button from '~/components/forms/buttons/Button'; import Input from '~/components/forms/Input'; import Toggle from '~/components/forms/Toggle'; import Loading from '~/components/Loading'; -import { createFlag, updateFlag } from '~/data/api'; import { useError } from '~/data/hooks/error'; +import { useAppDispatch } from '~/data/hooks/store'; import { useSuccess } from '~/data/hooks/success'; import { keyValidation, requiredValidation } from '~/data/validations'; import { FlagType, IFlag, IFlagBase } from '~/types/Flag'; import { classNames, copyTextToClipboard, stringAsKey } from '~/utils/helpers'; -type FlagFormProps = { - flag?: IFlag; - flagChanged?: () => void; -}; - const flagTypes = [ { id: FlagType.VARIANT, @@ -33,13 +29,15 @@ const flagTypes = [ } ]; -export default function FlagForm(props: FlagFormProps) { - const { flag, flagChanged } = props; +export default function FlagForm(props: { flag?: IFlag }) { + const { flag } = props; const isNew = flag === undefined; const submitPhrase = isNew ? 'Create' : 'Update'; const navigate = useNavigate(); + const dispatch = useAppDispatch(); + const { setError, clearError } = useError(); const { setSuccess } = useSuccess(); @@ -48,9 +46,17 @@ export default function FlagForm(props: FlagFormProps) { const handleSubmit = (values: IFlagBase) => { if (isNew) { - return createFlag(namespace.key, values); + return dispatch( + createFlagAsync({ namespaceKey: namespace.key, values: values }) + ).unwrap(); } - return updateFlag(namespace.key, flag?.key, values); + return dispatch( + updateFlagAsync({ + namespaceKey: namespace.key, + key: flag?.key, + values: values + }) + ).unwrap(); }; const initialValues: IFlagBase = { @@ -76,10 +82,7 @@ export default function FlagForm(props: FlagFormProps) { ); if (isNew) { navigate(`/namespaces/${namespace.key}/flags/${values.key}`); - return; } - - flagChanged && flagChanged(); }) .catch((err) => { setError(err); diff --git a/ui/src/components/flags/VariantForm.tsx b/ui/src/components/flags/VariantForm.tsx index a22c22e144..f3fceed2c3 100644 --- a/ui/src/components/flags/VariantForm.tsx +++ b/ui/src/components/flags/VariantForm.tsx @@ -4,14 +4,15 @@ import { Form, Formik } from 'formik'; import { forwardRef } from 'react'; import { useSelector } from 'react-redux'; import * as Yup from 'yup'; +import { createVariantAsync, updateVariantAsync } from '~/app/flags/flagsSlice'; import { selectCurrentNamespace } from '~/app/namespaces/namespacesSlice'; import Button from '~/components/forms/buttons/Button'; import Input from '~/components/forms/Input'; import TextArea from '~/components/forms/TextArea'; import Loading from '~/components/Loading'; import MoreInfo from '~/components/MoreInfo'; -import { createVariant, updateVariant } from '~/data/api'; import { useError } from '~/data/hooks/error'; +import { useAppDispatch } from '~/data/hooks/store'; import { useSuccess } from '~/data/hooks/success'; import { jsonValidation, keyValidation } from '~/data/validations'; import { IVariant, IVariantBase } from '~/types/Variant'; @@ -30,6 +31,8 @@ const VariantForm = forwardRef((props: VariantFormProps, ref: any) => { const title = isNew ? 'New Variant' : 'Edit Variant'; const submitPhrase = isNew ? 'Create' : 'Update'; + const dispatch = useAppDispatch(); + const { setError, clearError } = useError(); const { setSuccess } = useSuccess(); @@ -37,10 +40,23 @@ const VariantForm = forwardRef((props: VariantFormProps, ref: any) => { const handleSubmit = async (values: IVariantBase) => { if (isNew) { - return createVariant(namespace.key, flagKey, values); + return dispatch( + createVariantAsync({ + namespaceKey: namespace.key, + flagKey: flagKey, + values: values + }) + ).unwrap(); } - return updateVariant(namespace.key, flagKey, variant?.id, values); + return dispatch( + updateVariantAsync({ + namespaceKey: namespace.key, + flagKey: flagKey, + variantId: variant?.id, + values: values + }) + ).unwrap(); }; return ( diff --git a/ui/src/store.ts b/ui/src/store.ts index 57b2e10aae..c69f4650ba 100644 --- a/ui/src/store.ts +++ b/ui/src/store.ts @@ -4,6 +4,7 @@ import { isAnyOf } from '@reduxjs/toolkit'; +import { flagsSlice } from './app/flags/flagsSlice'; import { metaSlice } from './app/meta/metaSlice'; import { namespacesSlice } from './app/namespaces/namespacesSlice'; import { @@ -55,11 +56,17 @@ export const store = configureStore({ status: LoadingStatus.IDLE, currentNamespace, error: undefined + }, + flags: { + status: LoadingStatus.IDLE, + flags: {}, + error: undefined } }, reducer: { namespaces: namespacesSlice.reducer, preferences: preferencesSlice.reducer, + flags: flagsSlice.reducer, meta: metaSlice.reducer }, middleware: (getDefaultMiddleware) => From faf253c1df3d89d64c87a33d56489403790f1458 Mon Sep 17 00:00:00 2001 From: Mark Phelps <209477+markphelps@users.noreply.github.com> Date: Mon, 30 Oct 2023 13:48:05 -0400 Subject: [PATCH 2/4] feat: add subject to authentication metadata for auditing (#2299) * feat(wip): add subject to oidc token metadata for auditing * chore: add sub for github * chore: we use positive ints around here * chore: test for github sub --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- internal/server/auth/method/github/server.go | 12 +++++++----- internal/server/auth/method/github/server_test.go | 3 ++- internal/server/auth/method/oidc/server.go | 3 +++ internal/server/auth/method/oidc/server_test.go | 1 + internal/server/auth/server.go | 5 ++++- 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/internal/server/auth/method/github/server.go b/internal/server/auth/method/github/server.go index aec8d9e86e..bbeb9ac3df 100644 --- a/internal/server/auth/method/github/server.go +++ b/internal/server/auth/method/github/server.go @@ -35,6 +35,7 @@ const ( storageMetadataGithubEmail = "io.flipt.auth.github.email" storageMetadataGithubName = "io.flipt.auth.github.name" storageMetadataGithubPicture = "io.flipt.auth.github.picture" + storageMetadataGithubSub = "io.flipt.auth.github.sub" ) // Server is an Github server side handler. @@ -135,17 +136,14 @@ func (s *Server) Callback(ctx context.Context, r *auth.CallbackRequest) (*auth.C Name string `json:"name,omitempty"` Email string `json:"email,omitempty"` AvatarURL string `json:"avatar_url,omitempty"` + ID uint64 `json:"id,omitempty"` } if err := json.NewDecoder(userResp.Body).Decode(&githubUserResponse); err != nil { return nil, err } - metadata := map[string]string{ - storageMetadataGithubEmail: githubUserResponse.Email, - storageMetadataGithubName: githubUserResponse.Name, - storageMetadataGithubPicture: githubUserResponse.AvatarURL, - } + metadata := map[string]string{} if githubUserResponse.Name != "" { metadata[storageMetadataGithubName] = githubUserResponse.Name @@ -159,6 +157,10 @@ func (s *Server) Callback(ctx context.Context, r *auth.CallbackRequest) (*auth.C metadata[storageMetadataGithubPicture] = githubUserResponse.AvatarURL } + if githubUserResponse.ID != 0 { + metadata[storageMetadataGithubSub] = fmt.Sprintf("%d", githubUserResponse.ID) + } + clientToken, a, err := s.store.CreateAuthentication(ctx, &storageauth.CreateAuthenticationRequest{ Method: auth.Method_METHOD_GITHUB, ExpiresAt: timestamppb.New(time.Now().UTC().Add(s.config.Session.TokenLifetime)), diff --git a/internal/server/auth/method/github/server_test.go b/internal/server/auth/method/github/server_test.go index 6c1d810c3d..633af5131c 100644 --- a/internal/server/auth/method/github/server_test.go +++ b/internal/server/auth/method/github/server_test.go @@ -123,7 +123,7 @@ func TestServer_Github(t *testing.T) { MatchHeader("Accept", "application/vnd.github+json"). Get("/user"). Reply(200). - JSON(map[string]string{"name": "fliptuser", "email": "user@flipt.io", "avatar_url": "https://thispicture.com"}) + JSON(map[string]any{"name": "fliptuser", "email": "user@flipt.io", "avatar_url": "https://thispicture.com", "id": 1234567890}) c, err := client.Callback(ctx, &auth.CallbackRequest{Code: "github_code"}) require.NoError(t, err) @@ -134,6 +134,7 @@ func TestServer_Github(t *testing.T) { storageMetadataGithubEmail: "user@flipt.io", storageMetadataGithubName: "fliptuser", storageMetadataGithubPicture: "https://thispicture.com", + storageMetadataGithubSub: "1234567890", }, c.Authentication.Metadata) gock.Off() diff --git a/internal/server/auth/method/oidc/server.go b/internal/server/auth/method/oidc/server.go index 6916ec0bbc..bf745dae9a 100644 --- a/internal/server/auth/method/oidc/server.go +++ b/internal/server/auth/method/oidc/server.go @@ -25,6 +25,7 @@ const ( storageMetadataIDNameKey = "io.flipt.auth.oidc.name" storageMetadataIDProfileKey = "io.flipt.auth.oidc.profile" storageMetadataIDPictureKey = "io.flipt.auth.oidc.picture" + storageMetadataIDSubKey = "io.flipt.auth.oidc.sub" ) // errProviderNotFound is returned when a provider is requested which @@ -215,6 +216,7 @@ type claims struct { Name *string `json:"name"` Profile *string `json:"profile"` Picture *string `json:"picture"` + Sub *string `json:"sub"` } func (c claims) addToMetadata(m map[string]string) { @@ -228,6 +230,7 @@ func (c claims) addToMetadata(m map[string]string) { set(storageMetadataIDNameKey, c.Name) set(storageMetadataIDProfileKey, c.Profile) set(storageMetadataIDPictureKey, c.Picture) + set(storageMetadataIDSubKey, c.Sub) if c.Verified != nil { m[storageMetadataIDEmailVerifiedKey] = fmt.Sprintf("%v", *c.Verified) diff --git a/internal/server/auth/method/oidc/server_test.go b/internal/server/auth/method/oidc/server_test.go index d288c00b08..f8d5503b61 100644 --- a/internal/server/auth/method/oidc/server_test.go +++ b/internal/server/auth/method/oidc/server_test.go @@ -344,6 +344,7 @@ func testOIDCFlow(t *testing.T, ctx context.Context, tpAddr, clientAddress strin "io.flipt.auth.oidc.provider": "google", "io.flipt.auth.oidc.email": "mark@flipt.io", "io.flipt.auth.oidc.name": "Mark Phelps", + "io.flipt.auth.oidc.sub": "mark", }, response.Authentication.Metadata) // ensure expiry is set diff --git a/internal/server/auth/server.go b/internal/server/auth/server.go index a15c55d7e3..3c4835ea5d 100644 --- a/internal/server/auth/server.go +++ b/internal/server/auth/server.go @@ -16,7 +16,10 @@ import ( "google.golang.org/protobuf/types/known/timestamppb" ) -const ipKey = "x-forwarded-for" +const ( + ipKey = "x-forwarded-for" + subKey = "sub" +) var _ auth.AuthenticationServiceServer = &Server{} From 9cdbd6b93351a68044e59ce50488c93597d2f3e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 14:33:26 -0400 Subject: [PATCH 3/4] chore(deps): bump actions/setup-node from 3 to 4 (#2308) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-node dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- .github/workflows/nightly.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/snapshot.yml | 2 +- .github/workflows/test.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 547bac4077..52228a68bf 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -46,7 +46,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: "18" cache: "npm" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 0f3944b4d2..bf9587961f 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -37,7 +37,7 @@ jobs: restore-keys: | ${{ runner.os }}-go-tools- - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: "18" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c36a9f146e..d472727487 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,7 +37,7 @@ jobs: restore-keys: | ${{ runner.os }}-go-tools- - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: "18" diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml index 082f311c4f..efef8ea052 100644 --- a/.github/workflows/snapshot.yml +++ b/.github/workflows/snapshot.yml @@ -31,7 +31,7 @@ jobs: restore-keys: | ${{ runner.os }}-go-tools- - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: "18" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3a796be8e0..2b72f118a7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,7 +52,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: "18" cache: "npm" From 8a19f7f8cd6080091e01fcda04333969b184fa70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Oct 2023 18:47:04 -0400 Subject: [PATCH 4/4] chore(deps): bump github.com/redis/go-redis/v9 from 9.2.0 to 9.2.1 (#2309) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index ef0da1f49b..5659b9e840 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_golang v1.17.0 - github.com/redis/go-redis/v9 v9.2.0 + github.com/redis/go-redis/v9 v9.2.1 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/spf13/cobra v1.7.0 github.com/spf13/viper v1.17.0 diff --git a/go.sum b/go.sum index 2f74eb285b..910b04d378 100644 --- a/go.sum +++ b/go.sum @@ -615,8 +615,8 @@ github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/ github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0 h1:sadMIsgmHpEOGbUs6VtHBXRR1OHevnj7hLx9ZcdNGW4= github.com/protocolbuffers/txtpbfmt v0.0.0-20230328191034-3462fbc510c0/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= github.com/redis/go-redis/v9 v9.0.0-rc.4/go.mod h1:Vo3EsyWnicKnSKCA7HhgnvnyA74wOA69Cd2Meli5mmA= -github.com/redis/go-redis/v9 v9.2.0 h1:zwMdX0A4eVzse46YN18QhuDiM4uf3JmkOB4VZrdt5uI= -github.com/redis/go-redis/v9 v9.2.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= +github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=