React Native Rosetta Token

How can we obtain a Rosetta token during the second factor of authentication?

These code snippets demonstrate how the React Native application obtains a Rosetta JWT that can then be used to interact with the API endpoints. First, the RequestTokenRequest component and its supporting "props" give the user the opportunity to request the Rosetta JWT from the server. Then, the RequestTokenClaim component and its supporting "props" give the user the opportunity to claim the Rosetta JWT, after they have received the claim code in their email.

--

components/RequestTokenRequest.js

--

import React from 'react';

import {
  Body,
  Button, 
  Container, 
  Content, 
  Footer,
  Form,
  Header, 
  Input,
  Item,
  Label, 
  Title, 
  Text
} from 'native-base';
import { connect } from 'react-redux'

import {
  serverTokenRefreshUserThenRequestToken,
} from '../redux/actions-serverToken'

function RequestTokenRequest(props) {
  const handleSubmitTokenRequest = () => {
    props.serverTokenRefreshUserThenRequestToken();
  }

  return (
      <Container>
        <Header>
          <Body>
            <Title>Request Rosetta token</Title>
          </Body>
        </Header>

        <Content>
          <Form>
            <Item stackedLabel>
              <Label>Token Email</Label>
              <Input editable={false}>{props.requestTokenUserid}</Input>
            </Item>
            <Item stackedLabel>
              <Label>Token Expiration</Label>
              <Input editable={false}>{props.serverTokenExpiration || 'No token'}</Input>
            </Item>
            <Item>
              <Button rounded onPress={() => handleSubmitTokenRequest()} >
                <Text>OK</Text>
              </Button>
            </Item>
          </Form>
        </Content>

        <Footer>
        </Footer>
      </Container>
  );
}

const mapStateToProps = (state /*, ownProps*/) => {
  return {
    requestTokenUserid: state.overallAppReducer.appAmplifyUserIdJwtEmail,
    serverTokenExpiration: state.serverTokenReducer.serverTokenExpiration,
  }
}

const mapDispatchToProps = {
  serverTokenRefreshUserThenRequestToken,
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(RequestTokenRequest)

--

components/RequestTokenClaim.js

--

import React from 'react';
import { View } from 'react-native';

import {
  Body,
  Button, 
  Container, 
  Content, 
  Footer,
  Form,
  Header, 
  Input,
  Item,
  Label, 
  Title, 
  Text,
} from 'native-base';
import { connect } from 'react-redux'

import {
  serverTokenClaimTokenCancel,
  serverTokenRefreshUserThenClaimToken,
  serverTokenSetRequestTokenInfo,
} from '../redux/actions-serverToken'

function RequestTokenClaim(props) {
  const handleCancelTokenClaim = () => {
    props.serverTokenClaimTokenCancel();
  }

  const handleClaimValueChange = (text) => {
    let newRequestTokenInfo = {
      ...props.requestTokenInfo,
      'claimValue': text.trim()
    }
    props.serverTokenSetRequestTokenInfo(newRequestTokenInfo)
  }

  const handleSubmitTokenClaim = () => {
    props.serverTokenRefreshUserThenClaimToken();
  }

  return (
      <Container>
        <Header>
          <Body>
            <Title>Claim Rosetta token</Title>
          </Body>
        </Header>

        <Content>
          <Form>
            <View style={{ padding: 10 }}>
              <Label>Request Identifier</Label>
              <Text numberOfLines={1}>{props.requestId}</Text>
            </View>
            <View style={{ padding: 10 }}>
              <Label>Device Identifier</Label>
              <Text numberOfLines={1}>{props.deviceId}</Text>
            </View>
            <Item stackedLabel>
              <Label>Claim Value (from Email)</Label>
              <Input
                keyboardType="numeric"
                onChangeText={text => handleClaimValueChange(text)}
              />
            </Item>
            <View style={{ padding: 10 }}>
              <Item>
                <Button rounded onPress={() => handleSubmitTokenClaim()} >
                  <Text>OK</Text>
                </Button>
              </Item>
              <Item>
                <Button rounded onPress={() => handleCancelTokenClaim()} >
                  <Text>Cancel</Text>
                </Button>
              </Item>
            </View>
          </Form>
        </Content>

        <Footer>
        </Footer>
      </Container>
  );
}

const mapStateToProps = (state /*, ownProps*/) => {
  return {
    deviceId: state.serverTokenReducer.deviceId,
    requestId: state.serverTokenReducer.claimTokenRequestId,
    requestTokenInfo: state.serverTokenReducer.requestTokenInfo,
  }
}

const mapDispatchToProps = {
  serverTokenClaimTokenCancel,
  serverTokenRefreshUserThenClaimToken,
  serverTokenSetRequestTokenInfo,
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(RequestTokenClaim)

--

redux/actions-serverToken.js

--

import {
  SERVER_TOKEN_CLAIM_TOKEN_CANCEL,
  SERVER_TOKEN_CLAIM_TOKEN_ERROR,
  SERVER_TOKEN_CLAIM_TOKEN_STARTED,
  SERVER_TOKEN_CLAIM_TOKEN_SUCCESS,
  SERVER_TOKEN_REQUEST_TOKEN_ERROR,
  SERVER_TOKEN_REQUEST_TOKEN_STARTED,
  SERVER_TOKEN_REQUEST_TOKEN_SUCCESS,
  SERVER_TOKEN_SET_REQUEST_TOKEN_INFO,
} from './actionTypes'

import { Auth } from 'aws-amplify';
import { AsyncStorage } from 'react-native';

import { commonCreateAxiosClientWithAmplifyToken } from './actions-common'
import { overallAppGetUserError } from './actions-overallApp'
import { overallAppGetUserStarted } from './actions-overallApp'
import { overallAppGetUserSuccess } from './actions-overallApp'
import { parseServerToken } from './utility'

export function serverTokenClaimTokenCancel() {
  return { type: SERVER_TOKEN_CLAIM_TOKEN_CANCEL }
}

export function serverTokenClaimTokenError(errorResponse) {
  return { type: SERVER_TOKEN_CLAIM_TOKEN_ERROR, errorResponse }
}

export function serverTokenClaimTokenStarted() {
  return { type: SERVER_TOKEN_CLAIM_TOKEN_STARTED }
}

export function serverTokenClaimTokenSuccess(serverTokenJson) {
  return { type: SERVER_TOKEN_CLAIM_TOKEN_SUCCESS, serverTokenJson }
}

export function serverTokenRefreshUserThenClaimToken() {
  return (dispatch, getState) => {
    dispatch(overallAppGetUserStarted());
    Auth.currentAuthenticatedUser( { bypassCache: false } )
      .then(user => {
        dispatch(overallAppGetUserSuccess(user));
        dispatch(_serverTokenClaimToken());
      })
      .catch(err => { dispatch(overallAppGetUserError(err.response)); });
  }
}

export function serverTokenRefreshUserThenRequestToken() {
  return (dispatch, getState) => {
    dispatch(overallAppGetUserStarted());
    Auth.currentAuthenticatedUser( { bypassCache: false } )
      .then(user => {
        dispatch(overallAppGetUserSuccess(user));
        dispatch(_serverTokenRequestToken());
      })
      .catch(err => { dispatch(overallAppGetUserError(err.response)); });
  }
}

export function serverTokenRequestTokenError(errorResponse) {
  return { type: SERVER_TOKEN_REQUEST_TOKEN_ERROR, errorResponse }
}

export function serverTokenRequestTokenStarted() {
  return { type: SERVER_TOKEN_REQUEST_TOKEN_STARTED }
}

export function serverTokenRequestTokenSuccess(tokenRequestId) {
  return { type: SERVER_TOKEN_REQUEST_TOKEN_SUCCESS, tokenRequestId }
}

export function serverTokenSetRequestTokenInfo(requestTokenInfo) {
  return { type: SERVER_TOKEN_SET_REQUEST_TOKEN_INFO, requestTokenInfo }
}

// Protected Functions

function _serverTokenClaimToken() {
  return (dispatch, getState) => {
    dispatch(serverTokenClaimTokenStarted());
    const axiosClient = commonCreateAxiosClientWithAmplifyToken(getState());
    const reducer = getState().serverTokenReducer;
    const tokenClaimInfo = {
      'deviceId': reducer.deviceId,
      'claimValue': reducer.requestTokenInfo.claimValue,
    }
    axiosClient
      .patch("/api/token-requests/" + reducer.claimTokenRequestId, tokenClaimInfo)
      .then(res => {
        let bearerTokenValue = res.data;
        console.log("Bearer token value: " + bearerTokenValue);
        let serverTokenJson = parseServerToken(bearerTokenValue);
        console.log("Server token JSON: " + serverTokenJson);
        _serverTokenUpdatePersistentServerToken(serverTokenJson)
          .then( dispatch(serverTokenClaimTokenSuccess(serverTokenJson)) );
      })
      .catch(err => { dispatch(serverTokenClaimTokenError(err.response)) });
  }
}

function _serverTokenRequestToken() {
  return (dispatch, getState) => {
    dispatch(serverTokenRequestTokenStarted());
    const axiosClient = commonCreateAxiosClientWithAmplifyToken(getState());
    const appReducer = getState().overallAppReducer;
    const tokenReducer = getState().serverTokenReducer;
    const tokenRequestInfo = {
      'deviceId': tokenReducer.deviceId,
      'durationSeconds': tokenReducer.requestTokenInfo.durationHours * 60 * 60,
      'userId': appReducer.appAmplifyUserIdJwtEmail,
    }
    axiosClient
      .post("/api/token-requests", tokenRequestInfo)
      .then(res => { dispatch(serverTokenRequestTokenSuccess(res.data)) })
      .catch(err => { dispatch(serverTokenRequestTokenError(err.response)) });
  }
}

// Helper Functions

async function _serverTokenUpdatePersistentServerToken(serverTokenJson) {
  const valueKey = 'persistentServerToken';
  const valueJson = serverTokenJson || {};
  const valueString = JSON.stringify(valueJson);
  console.log("New " + valueKey + ": " + valueString);
  await AsyncStorage.setItem(valueKey, valueString);
}

--

redux/reducers-serverToken.js

--

import {
  SERVER_TOKEN_CLAIM_TOKEN_ERROR,
  SERVER_TOKEN_CLAIM_TOKEN_STARTED,
  SERVER_TOKEN_CLAIM_TOKEN_SUCCESS,
  SERVER_TOKEN_REQUEST_TOKEN_ERROR,
  SERVER_TOKEN_REQUEST_TOKEN_STARTED,
  SERVER_TOKEN_REQUEST_TOKEN_SUCCESS,
} from './actionTypes'

import { commonGetErrorMessage } from './reducers-common'
import { isTokenExpired } from './utility'

const serverTokenInitialState = {
  claimTokenError: '',
  claimTokenRequestId: '',
  
  deviceId: '',
  
  installTokenError: '',
  
  requestTokenError: '',
  requestTokenInfo: {
    deviceId: '',
    durationHours: 10,
    claimValue: '',
  },
  
  serverBaseUri: "https://api.rosettasalt.org",
  
  serverTokenIdentifier: '',
  serverTokenExpiration: '',
  serverTokenExpirationSeconds: 0,
  serverTokenExpired: true,
  serverTokenValue: '',
  
  waitingForServer: false,
}

export function serverTokenReducer(state = serverTokenInitialState, action) {
  let errorMessage = "";
  switch (action.type) {
    case SERVER_TOKEN_CLAIM_TOKEN_ERROR:
      errorMessage = commonGetErrorMessage(action.errorResponse);
      console.log("Claim token error: " + errorMessage)
      return { 
        ...state, 
        claimTokenError: errorMessage,
        waitingForServer: false,
      }
    case SERVER_TOKEN_CLAIM_TOKEN_STARTED:
      return { 
        ...state, 
        claimTokenError: '',
        installTokenError: '',
        waitingForServer: true,
      }
    case SERVER_TOKEN_CLAIM_TOKEN_SUCCESS:
      let newServerToken = action.serverTokenJson;
      return { 
        ...state, 
        installTokenError: newServerToken.serverTokenError,
        serverTokenIdentifier: newServerToken.serverTokenIdentifier,
        serverTokenExpiration: newServerToken.serverTokenExpiration,
        serverTokenExpirationSeconds: newServerToken.serverTokenExpirationSeconds,
        serverTokenExpired: isTokenExpired(newServerToken.serverTokenExpirationSeconds),
        serverTokenValue: newServerToken.serverTokenValue,
        waitingForServer: false,
      }
    case SERVER_TOKEN_REQUEST_TOKEN_ERROR:
      errorMessage = commonGetErrorMessage(action.errorResponse);
      console.log("Request token error: " + errorMessage);
      return { 
        ...state, 
        requestTokenError: errorMessage,
        waitingForServer: false,
      }
    case SERVER_TOKEN_REQUEST_TOKEN_STARTED:
      let newRequestTokenInfo = {
        deviceId: state.deviceId || '',
        durationHours: 10,
        claimValue: '',
      };
      return { 
        ...state, 
        claimTokenError: '',
        requestTokenError: '',
        requestTokenInfo: newRequestTokenInfo,
        serverTokenExpired: true,
        waitingForServer: true,
      }
    case SERVER_TOKEN_REQUEST_TOKEN_SUCCESS:
      console.log("Token request successful...claim request id: " + action.tokenRequestId);
      return { 
        ...state, 
        claimTokenRequestId: action.tokenRequestId,
        waitingForServer: false,
      }
    default:
      return state
  }
}

--