Home > Back-end >  Replace mock credentials in S3Client with redux async thunk
Replace mock credentials in S3Client with redux async thunk

Time:03-03

I have an S3-like react-application with redux. I'm using AWS SDK v3 for JS, and initializing my client like this:

auth.js

export const s3Client = new S3Client({
  region: 'default',
  credentials: {
    accessKeyId: 'testTestAccess',
    secretAccessKey: 'testTestSecret'
  },
  endpoint: `${document.URL}s3/`
});

My requests go through our proxy, that's the reason why i can leave credentials exactly like in code above, so it can be any string. But i'm using s3 signed url, and it uses credentials in query strings.

That's how i send my request with redux-thunk

authReducer.js

const initialState = {
  secretKey: 'initSecretKey',
  accessKey: 'initAccessKey',
  keysCreated: false
};

export const fetchKeys = createAsyncThunk(
  'auth/fetchKeys',
  async (_, { rejectWithValue }) => {
    try {
      const response = await axiosInstance.get();

      if (response.statusText !== 'OK') {
        throw new Error('Error!');
      }

      return response.data.secrets;
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

export const authSlice = createSlice({
  name: 'auth',
  initialState,
  extraReducers: {
    [fetchKeys.pending]: (state, action) => {
      console.log('PENDING...');
    },
    [fetchKeys.fulfilled]: (state, action) => {
      const { AccessKey, SecretKey } = action.payload;
      state.secretKey = SecretKey;
      state.accessKey = AccessKey;
      state.keysCreated = true;
    },
    [fetchKeys.rejected]: (state, action) => {
      console.log('ERROR');
    }
  },
  reducers: {
    setSecretKey: (state, action) => ({
      ...state,
      secretKey: action.payload
    }),
    setAccessKey: (state, action) => ({
      ...state,
      accessKey: action.payload
    }),
    setKeysCreated: (state, action) => ({
      ...state,
      keysCreated: action.payload
    })
  }
});

export const { setSecretKey, setAccessKey, setKeysCreated } =
  authSlice.actions;

export default authSlice.reducer;

MainPage.jsx

const MainPage = () => {
  const dispatch = useDispatch();
  const { keysCreated } = useSelector((state) => state.authReducer);

  useEffect(() => {
    dispatch(fetchKeys());
  }, [dispatch]);

  if (keysCreated) {
    return <Content />
  }

  return <Loader />
};

So what i want to do:

  1. When the page is rendering, i'm sending an async request to keygen with axios and redux-thunk
  2. Showing the page only if i got the keys
  3. (!) Replace mock keys in s3 client instance with new keys from keygen, when request is succeeded, so that i can make signed urls.

How can i do that? I get keys from store only once, so looks like i need subscribe on changes

const { secretKey, accessKey } = store.getState().authReducer;

export const s3Client = new S3Client({
  credentials: {
    accessKeyId: accessKey,     // 'initAccessKey' from initial state
    secretAccessKey: secretKey  // 'initSecretKey' from initial state
  }
});

CodePudding user response:

I resolved my problem with hook.

auth.js

export const s3Client = new S3Client({
  region: 'default',
  credentials: {
    accessKeyId: 'initAccessKey',
    secretAccessKey: 'initSecretKey'
  }
});

export const useS3Client = () => {
  const { accessKey, secretKey, keysCreated } = useSelector(
    (state) => state.authReducer
  );

  const client = useMemo(() => {
    if (keysCreated) {
      return new S3Client({
        region: 'default',
        credentials: {
          accessKeyId: accessKey,
          secretAccessKey: secretKey
        }
      });
    }

    return s3Client;
  }, [accessKey, secretKey, keysCreated]);

  return [client, keysCreated];
};

I changed a little my functions for API signed requests:

// i added client as 1st argument
export const getUrl = async (client, object, expirationSec = 300) => {
    const { name, absolutePath, parentBucket } = object;

    const params = {
      Bucket: parentBucket,
      Key: absolutePath
    };

    return await getSignedUrl(client, new GetObjectCommand(params), {
      expiresIn: expirationSec
    }).catch((error) => setError(error, 'SIGNED URL GETTING FAILED'));
  }

And then i can use it in my components:

MainPage.jsx

const MainPageContainer = () => {
  const dispatch = useDispatch();
  const [, keysCreated] = useS3Client();

  useEffect(() => {
    dispatch(fetchKeys());
  }, [dispatch]);

  if (!keysCreated) return <Loader />;

  return <MainPage />;
};

SignUrlForm.jsx

const SignUrlForm = () => {
  const [client] = useS3Client();

  const onGetSignedUrlClick = (object) => {
    getUrl(client, object, intevalSec).then((url) => {
      // some actions
    });
  };

  return <Button onClick={onGetSignedUrlClick}>Sign URL</Button>;
};
  • Related