mirror of
https://github.com/standardnotes/mobile.git
synced 2026-04-19 21:58:51 -04:00
Privileges Management UI
This commit is contained in:
@@ -19,6 +19,7 @@ import MainSideMenu from "@SideMenu/MainSideMenu"
|
||||
import NoteSideMenu from "@SideMenu/NoteSideMenu"
|
||||
import Settings from "@Screens/Settings/Settings"
|
||||
import InputModal from "@Screens/InputModal"
|
||||
import ManagePrivileges from "@Screens/ManagePrivileges"
|
||||
import Authenticate from "@Screens/Authentication/Authenticate"
|
||||
|
||||
import SideMenuManager from "@SideMenu/SideMenuManager"
|
||||
@@ -76,12 +77,16 @@ const AuthenticateModalStack = createStackNavigator({
|
||||
Screen1: Authenticate
|
||||
})
|
||||
|
||||
const ManagePrivilegesStack = createStackNavigator({
|
||||
Screen1: ManagePrivileges
|
||||
})
|
||||
|
||||
const AppDrawer = createStackNavigator({
|
||||
Home: AppDrawerStack,
|
||||
Settings: SettingsStack,
|
||||
InputModal: InputModalStack,
|
||||
Authenticate: AuthenticateModalStack,
|
||||
|
||||
ManagePrivileges: ManagePrivilegesStack
|
||||
}, {
|
||||
mode: "modal",
|
||||
headerMode: 'none',
|
||||
|
||||
@@ -59,6 +59,9 @@ export default class PrivilegesManager extends SFPrivilegesManager {
|
||||
|
||||
let sources = await this.sourcesForAction(action);
|
||||
|
||||
let sessionLengthOptions = await this.getSessionLengthOptions();
|
||||
let selectedSessionLength = await this.getSelectedSessionLength();
|
||||
|
||||
navigation.navigate("Authenticate", {
|
||||
leftButton: {
|
||||
title: ApplicationState.isIOS ? "Cancel" : null,
|
||||
@@ -66,7 +69,10 @@ export default class PrivilegesManager extends SFPrivilegesManager {
|
||||
},
|
||||
authenticationSources: sources,
|
||||
hasCancelOption: true,
|
||||
onSuccess: () => {
|
||||
sessionLengthOptions: sessionLengthOptions,
|
||||
selectedSessionLength: selectedSessionLength,
|
||||
onSuccess: (selectedSessionLength) => {
|
||||
this.setSessionLength(selectedSessionLength);
|
||||
customSuccess();
|
||||
},
|
||||
onCancel: () => {
|
||||
|
||||
@@ -35,6 +35,8 @@ export default class Authenticate extends Abstract {
|
||||
source.initializeForInterface();
|
||||
}
|
||||
|
||||
this._sessionLength = this.getProp("selectedSessionLength");
|
||||
|
||||
// if(__DEV__) {
|
||||
// props.navigation.setParams({
|
||||
// leftButton: {
|
||||
@@ -131,7 +133,7 @@ export default class Authenticate extends Abstract {
|
||||
componentWillBlur() {
|
||||
super.componentWillBlur();
|
||||
if(this.successful) {
|
||||
this.getProp("onSuccess")();
|
||||
this.getProp("onSuccess")(this._sessionLength);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +142,15 @@ export default class Authenticate extends Abstract {
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
get sessionLengthOptions() {
|
||||
return this.getProp("sessionLengthOptions");
|
||||
}
|
||||
|
||||
setSessionLength(length) {
|
||||
this._sessionLength = length;
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
_renderAuthenticationSoure = (source, index) => {
|
||||
|
||||
let isLast = index == this.sources.length - 1;
|
||||
@@ -215,6 +226,22 @@ export default class Authenticate extends Abstract {
|
||||
onPress={() => this.submitPressed()}
|
||||
/>
|
||||
|
||||
{this.sessionLengthOptions && this.sessionLengthOptions.length > 0 &&
|
||||
<View style={this.styles.rememberForSection}>
|
||||
<SectionHeader title={"Remember For"} />
|
||||
{this.sessionLengthOptions.map((option, index) =>
|
||||
<SectionedAccessoryTableCell
|
||||
text={option.label}
|
||||
key={`${index}`}
|
||||
first={index == 0}
|
||||
last={index == this.sessionLengthOptions.length - 1}
|
||||
selected={() => {return option.value == this._sessionLength}}
|
||||
onPress={() => {this.setSessionLength(option.value)}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
|
||||
</ScrollView>
|
||||
</View>
|
||||
)
|
||||
@@ -222,12 +249,11 @@ export default class Authenticate extends Abstract {
|
||||
|
||||
loadStyles() {
|
||||
this.styles = {
|
||||
authSourceSection: {
|
||||
},
|
||||
authSourceSectionNotLast: {
|
||||
marginBottom: 10
|
||||
},
|
||||
submitButtonCell: {
|
||||
rememberForSection: {
|
||||
marginTop: 10
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
169
src/screens/ManagePrivileges.js
Normal file
169
src/screens/ManagePrivileges.js
Normal file
@@ -0,0 +1,169 @@
|
||||
import React, { Component } from 'react';
|
||||
import { TextInput, View, Text, Platform, SafeAreaView, ScrollView } from 'react-native';
|
||||
import StyleKit from "@Style/StyleKit"
|
||||
import TableSection from "@Components/TableSection";
|
||||
import SectionedTableCell from "@Components/SectionedTableCell";
|
||||
import SectionedOptionsTableCell from "@Components/SectionedOptionsTableCell";
|
||||
import SectionedAccessoryTableCell from "@Components/SectionedAccessoryTableCell"
|
||||
import SectionHeader from "@Components/SectionHeader";
|
||||
import ButtonCell from "@Components/ButtonCell";
|
||||
import Abstract from "@Screens/Abstract"
|
||||
import LockedView from "@Containers/LockedView";
|
||||
import ApplicationState from "@Lib/ApplicationState"
|
||||
import Auth from "@SFJS/authManager"
|
||||
import KeysManager from "@Lib/keysManager"
|
||||
import PrivilegesManager from "@SFJS/privilegesManager"
|
||||
|
||||
export default class ManagePrivileges extends Abstract {
|
||||
|
||||
static navigationOptions = ({ navigation, navigationOptions }) => {
|
||||
let templateOptions = {
|
||||
title: "Privileges",
|
||||
leftButton: {
|
||||
title: ApplicationState.isIOS ? "Done" : null,
|
||||
iconName: ApplicationState.isIOS ? null : StyleKit.nameForIcon("checkmark"),
|
||||
}
|
||||
}
|
||||
return Abstract.getDefaultNavigationOptions({navigation, navigationOptions, templateOptions});
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
availableActions: [],
|
||||
availableCredentials: []
|
||||
}
|
||||
|
||||
props.navigation.setParams({
|
||||
leftButton: {
|
||||
title: ApplicationState.isIOS ? "Done" : null,
|
||||
iconName: ApplicationState.isIOS ? null : StyleKit.nameForIcon("checkmark"),
|
||||
onPress: () => {
|
||||
this.dismiss();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.hasPasscode = KeysManager.get().hasOfflinePasscode();
|
||||
this.hasAccount = !Auth.get().offline();
|
||||
|
||||
this.reloadPrivileges();
|
||||
}
|
||||
|
||||
displayInfoForCredential(credential) {
|
||||
return PrivilegesManager.get().displayInfoForCredential(credential).label;
|
||||
}
|
||||
|
||||
displayInfoForAction(action) {
|
||||
return PrivilegesManager.get().displayInfoForAction(action).label;
|
||||
}
|
||||
|
||||
isCredentialRequiredForAction(action, credential) {
|
||||
if(!this.privileges) {
|
||||
return false;
|
||||
}
|
||||
return this.privileges.isCredentialRequiredForAction(action, credential);
|
||||
}
|
||||
|
||||
clearSession = () => {
|
||||
PrivilegesManager.get().clearSession().then(() => {
|
||||
this.reloadPrivileges();
|
||||
})
|
||||
}
|
||||
|
||||
async reloadPrivileges() {
|
||||
this.privileges = await PrivilegesManager.get().getPrivileges();
|
||||
let availableCredentials = PrivilegesManager.get().getAvailableCredentials();
|
||||
let availableActions = PrivilegesManager.get().getAvailableActions();
|
||||
|
||||
this.credentialDisplayInfo = {};
|
||||
for(let cred of availableCredentials) {
|
||||
this.credentialDisplayInfo[cred] = this.displayInfoForCredential(cred);
|
||||
}
|
||||
|
||||
let sessionEndDate = await PrivilegesManager.get().getSessionExpirey();
|
||||
this.setState({
|
||||
availableActions: availableActions,
|
||||
availableCredentials: availableCredentials,
|
||||
sessionExpirey: sessionEndDate.toLocaleString(),
|
||||
sessionExpired: new Date() >= sessionEndDate
|
||||
});
|
||||
}
|
||||
|
||||
valueChanged(action, credential) {
|
||||
this.privileges.toggleCredentialForAction(action, credential);
|
||||
PrivilegesManager.get().savePrivileges();
|
||||
this.forceUpdate();
|
||||
}
|
||||
|
||||
render() {
|
||||
if(this.state.lockContent) {
|
||||
return (<LockedView />);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={StyleKit.styles.container}>
|
||||
<ScrollView>
|
||||
|
||||
{this.state.sessionExpirey && !this.state.sessionExpired &&
|
||||
<View style={this.styles.section}>
|
||||
<SectionHeader title={"Current Session"} />
|
||||
<SectionedTableCell first={true}>
|
||||
<Text style={[this.styles.cellText]}>
|
||||
You will not be asked to authenticate until {this.state.sessionExpirey}.
|
||||
</Text>
|
||||
</SectionedTableCell>
|
||||
<ButtonCell last={true} leftAligned={true} title={`Clear Local Session`} onPress={this.clearSession} />
|
||||
</View>
|
||||
}
|
||||
|
||||
{this.state.availableActions.map((action, actionIndex) =>
|
||||
<View style={this.styles.section} key={`${actionIndex}`}>
|
||||
<SectionHeader title={this.displayInfoForAction(action)} />
|
||||
{this.state.availableCredentials.map((credential, credIndex) =>
|
||||
<SectionedAccessoryTableCell
|
||||
text={this.displayInfoForCredential(credential)}
|
||||
key={`${actionIndex}+${credIndex}`}
|
||||
first={credIndex == 0}
|
||||
last={credIndex == this.state.availableCredentials.length - 1}
|
||||
selected={() => {return this.isCredentialRequiredForAction(action, credential)}}
|
||||
onPress={() => {this.valueChanged(action, credential)}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
<View style={this.styles.section}>
|
||||
<SectionHeader title={"About Privileges"} />
|
||||
<SectionedTableCell first={true} last={true}>
|
||||
<Text style={[this.styles.aboutText, this.styles.cellText]}>
|
||||
Privileges represent interface level authentication for accessing certain items and features. Privileges are meant to protect against unwanted access in the event of an unlocked application, but do not affect data encryption state.
|
||||
</Text>
|
||||
<Text style={[this.styles.aboutText, this.styles.cellText]}>
|
||||
Privileges sync across your other devices—however, note that if you require a "Local Passcode" privilege, and another device does not have a local passcode set up, the local passcode requirement will be ignored on that device.
|
||||
</Text>
|
||||
</SectionedTableCell>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
loadStyles() {
|
||||
this.styles = {
|
||||
section: {
|
||||
marginBottom: 8
|
||||
},
|
||||
cellText: {
|
||||
lineHeight: 19,
|
||||
fontSize: 16,
|
||||
color: StyleKit.variables.stylekitForegroundColor,
|
||||
},
|
||||
aboutText: {
|
||||
marginBottom: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -95,13 +95,15 @@ class OptionsSection extends Abstract {
|
||||
|
||||
<SectionHeader title={this.props.title} />
|
||||
|
||||
<ButtonCell first={true} leftAligned={true} title={`Manage Privileges`} onPress={this.props.onManagePrivileges} />
|
||||
|
||||
{signedIn &&
|
||||
<ButtonCell first={true} leftAligned={true} title={`Sign out (${this.state.email})`} onPress={this.props.onSignOutPress} />
|
||||
<ButtonCell leftAligned={true} title={`Sign out (${this.state.email})`} onPress={this.props.onSignOutPress} />
|
||||
}
|
||||
|
||||
<SectionedOptionsTableCell
|
||||
last={!hasLastExportSection}
|
||||
first={!signedIn}
|
||||
first={false}
|
||||
disabled={this.state.loadingExport}
|
||||
leftAligned={true}
|
||||
options={this.exportOptions()}
|
||||
|
||||
@@ -250,6 +250,7 @@ export default class Settings extends Abstract {
|
||||
<OptionsSection
|
||||
title={"Options"}
|
||||
onSignOutPress={this.onSignOutPress}
|
||||
onManagePrivileges={() => {this.props.navigation.navigate("ManagePrivileges")}}
|
||||
/>
|
||||
|
||||
<TableSection>
|
||||
|
||||
Reference in New Issue
Block a user