feat: add some 2FA TOTP code (#2)
* feat: add 2FA TOTP. * fix: use npmjs.
This commit is contained in:
parent
b575ab96f1
commit
39674fa9c7
|
@ -0,0 +1,50 @@
|
||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
|
import {default as hotptotp} from 'hotp-totp';
|
||||||
|
const {totp} = hotptotp;
|
||||||
|
window.Buffer = window.Buffer || require("buffer").Buffer;
|
||||||
|
|
||||||
|
class Account {
|
||||||
|
constructor(description, secretCode, onUpdate) {
|
||||||
|
this.title = description;
|
||||||
|
this.secretCode = secretCode;
|
||||||
|
this.countdowns = 30;
|
||||||
|
this.timer = setInterval(this.updateCountdown.bind(this), 1000);
|
||||||
|
this.token = '';
|
||||||
|
this.tokenInterval = setInterval(this.generateAndSetToken.bind(this), 30000);
|
||||||
|
this.onUpdate = onUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateToken = async () => {
|
||||||
|
let token = await totp(this.secretCode);
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateAndSetToken = async () => {
|
||||||
|
this.token = await this.generateToken();
|
||||||
|
this.onUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCountdown() {
|
||||||
|
this.countdowns = Math.max(0, this.countdowns - 1);
|
||||||
|
if (this.countdowns === 0) {
|
||||||
|
this.countdowns = 30;
|
||||||
|
this.onUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Account;
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { View, Text, TextInput } from 'react-native';
|
||||||
|
import { Button, IconButton } from 'react-native-paper';
|
||||||
|
|
||||||
|
export default function EnterAccountDetails({ onClose, onAdd }) {
|
||||||
|
const [description, setDescription] = useState('');
|
||||||
|
const [secretCode, setSecretCode] = useState('');
|
||||||
|
|
||||||
|
const handleAddAccount = () => {
|
||||||
|
onAdd({ description, secretCode });
|
||||||
|
setDescription('');
|
||||||
|
setSecretCode('');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||||
|
<Text style={{fontSize: 24, marginBottom: 5}}>Add new 2FA account</Text>
|
||||||
|
<div style={{display: 'flex', marginTop: 10}}>
|
||||||
|
<IconButton icon='account-details' size={35}></IconButton>
|
||||||
|
<TextInput
|
||||||
|
placeholder='Description'
|
||||||
|
value={description}
|
||||||
|
onChangeText={(text) => setDescription(text)}
|
||||||
|
style={{ borderWidth: 3, borderColor: 'white', margin: 10, width: 230, height: 50, borderRadius: 5, fontSize: 18,
|
||||||
|
color: 'gray', paddingLeft: 10}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{display: 'flex'}}>
|
||||||
|
<IconButton icon='account-key' size={35}></IconButton>
|
||||||
|
<TextInput
|
||||||
|
placeholder='Secret code'
|
||||||
|
value={secretCode}
|
||||||
|
onChangeText={(text) => setSecretCode(text)}
|
||||||
|
secureTextEntry
|
||||||
|
style={{ borderWidth: 3, borderColor: 'white', margin: 10, width: 230, height: 50, borderRadius: 5, fontSize: 18,
|
||||||
|
color: 'gray', paddingLeft: 10 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
icon='account-plus'
|
||||||
|
style={{
|
||||||
|
backgroundColor: '#393544',
|
||||||
|
borderRadius: 5,
|
||||||
|
margin: 10,
|
||||||
|
alignItems: 'center',
|
||||||
|
position: 'absolute',
|
||||||
|
top: 260,
|
||||||
|
width: 300,
|
||||||
|
// height: 50
|
||||||
|
}}
|
||||||
|
onPress={handleAddAccount}
|
||||||
|
>
|
||||||
|
<Text style={{fontSize: 18}}>Add</Text>
|
||||||
|
</Button>
|
||||||
|
<IconButton icon={'close'} size={30} onPress={onClose} style={{position: 'absolute', top: 5, right: 5}} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
218
HomePage.js
218
HomePage.js
|
@ -13,73 +13,169 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {Avatar, List} from "react-native-paper";
|
import { View, TouchableOpacity, Text, FlatList } from 'react-native';
|
||||||
import SearchBar from "./SearchBar";
|
import { Avatar, List, Portal, Modal, IconButton } from 'react-native-paper';
|
||||||
|
import SearchBar from './SearchBar';
|
||||||
|
|
||||||
|
import EnterAccountDetails from './EnterAccountDetails';
|
||||||
|
import Account from "./Account";
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
|
const [isPlusButton, setIsPlusButton] = React.useState(true);
|
||||||
|
const [showOptions, setShowOptions] = React.useState(false);
|
||||||
|
const [showEnterAccountModal, setShowEnterAccountModal] = React.useState(false);
|
||||||
|
const [accountList, setAccountList] = React.useState([]);
|
||||||
|
const [searchQuery, setSearchQuery] = React.useState('');
|
||||||
|
const [filteredData, setFilteredData] = React.useState(accountList);
|
||||||
|
|
||||||
|
const togglePlusButton = () => {
|
||||||
|
setIsPlusButton(!isPlusButton);
|
||||||
|
setShowOptions(!showOptions);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeOptions = () => {
|
||||||
|
setIsPlusButton(true);
|
||||||
|
setShowOptions(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const openEnterAccountModal = () => {
|
||||||
|
setShowEnterAccountModal(true);
|
||||||
|
closeOptions();
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeEnterAccountModal = () => {
|
||||||
|
setShowEnterAccountModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddAccount = async (accountData) => {
|
||||||
|
const onUpdate = () => {
|
||||||
|
setAccountList(prevList => [...prevList]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const newAccount = new Account(accountData.description, accountData.secretCode, onUpdate);
|
||||||
|
const token = await newAccount.generateToken();
|
||||||
|
newAccount.token = token;
|
||||||
|
|
||||||
|
await setAccountList(prevList => [...prevList, newAccount]);
|
||||||
|
closeEnterAccountModal();
|
||||||
|
};
|
||||||
|
React.useEffect(() => {
|
||||||
|
setAccountList(prevList => [...prevList]);
|
||||||
|
}, [accountList]);
|
||||||
|
|
||||||
|
|
||||||
|
const handleSearch = (query) => {
|
||||||
|
setSearchQuery(query);
|
||||||
|
|
||||||
|
if (query.trim() !== '') {
|
||||||
|
const filteredResults = accountList.filter(item =>
|
||||||
|
item.title.toLowerCase().includes(query.toLowerCase())
|
||||||
|
);
|
||||||
|
setFilteredData(filteredResults);
|
||||||
|
} else {
|
||||||
|
setFilteredData(accountList);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<View style={{ flex: 1 }}>
|
||||||
<SearchBar />
|
<SearchBar onSearch={ handleSearch } />
|
||||||
|
<FlatList
|
||||||
|
// data={accountList}
|
||||||
|
data={searchQuery.trim() !== '' ? filteredData : accountList}
|
||||||
|
keyExtractor={(item, index) => index.toString()}
|
||||||
|
renderItem={({ item }) => (
|
||||||
<List.Item
|
<List.Item
|
||||||
title="Casdoor"
|
title={
|
||||||
description="admin"
|
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||||
left={props => <Avatar.Image size={24} style={{marginLeft: '20px', backgroundColor: 'rgb(242,242,242)'}} source={'https://cdn.casbin.org/img/social_casdoor.png'} />}
|
<Text style={{ fontSize: 20, width: 80 }}>{item.title}</Text>
|
||||||
|
<Text style={{ marginLeft: 20, fontSize: 30 }}>{item.token}</Text>
|
||||||
|
<Text style={{ marginLeft: 20, fontSize: 20, width: 20 }}>{item.countdowns}s</Text>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
left={(props) => (
|
||||||
|
<Avatar.Image
|
||||||
|
size={60}
|
||||||
|
style={{ marginLeft: '20px', backgroundColor: 'rgb(242,242,242)' }}
|
||||||
|
source={'https://cdn.casbin.org/img/social_casdoor.png'}
|
||||||
/>
|
/>
|
||||||
<List.Item
|
)}
|
||||||
title="GitHub"
|
|
||||||
description="Linus"
|
|
||||||
left={props => <Avatar.Image size={24} style={{marginLeft: '20px', backgroundColor: 'rgb(242,242,242)'}} source={'https://cdn.casbin.org/img/social_github.png'} />}
|
|
||||||
/>
|
/>
|
||||||
<List.Item
|
)}
|
||||||
title="Google"
|
|
||||||
description="James Greenson"
|
|
||||||
left={props => <Avatar.Image size={24} style={{marginLeft: '20px', backgroundColor: 'rgb(242,242,242)'}} source={'https://cdn.casbin.org/img/social_google.png'} />}
|
|
||||||
/>
|
/>
|
||||||
<List.Item
|
|
||||||
title="Casdoor"
|
<Portal>
|
||||||
description="admin"
|
<Modal
|
||||||
left={props => <Avatar.Image size={24} style={{marginLeft: '20px', backgroundColor: 'rgb(242,242,242)'}} source={'https://cdn.casbin.org/img/social_casdoor.png'} />}
|
visible={showOptions}
|
||||||
/>
|
onDismiss={closeOptions}
|
||||||
<List.Item
|
contentContainerStyle={{
|
||||||
title="GitHub"
|
backgroundColor: 'white',
|
||||||
description="Linus"
|
padding: 20,
|
||||||
left={props => <Avatar.Image size={24} style={{marginLeft: '20px', backgroundColor: 'rgb(242,242,242)'}} source={'https://cdn.casbin.org/img/social_github.png'} />}
|
borderRadius: 10,
|
||||||
/>
|
width: 300,
|
||||||
<List.Item
|
height: 150,
|
||||||
title="Google"
|
position: 'absolute',
|
||||||
description="James Greenson"
|
top: '50%',
|
||||||
left={props => <Avatar.Image size={24} style={{marginLeft: '20px', backgroundColor: 'rgb(242,242,242)'}} source={'https://cdn.casbin.org/img/social_google.png'} />}
|
left: '50%',
|
||||||
/>
|
transform: [{ translateX: '-50%' }, { translateY: '-50%' }],
|
||||||
<List.Item
|
}}
|
||||||
title="Casdoor"
|
>
|
||||||
description="admin"
|
<TouchableOpacity
|
||||||
left={props => <Avatar.Image size={24} style={{marginLeft: '20px', backgroundColor: 'rgb(242,242,242)'}} source={'https://cdn.casbin.org/img/social_casdoor.png'} />}
|
style={{ flexDirection: 'row', alignItems: 'center'}}
|
||||||
/>
|
onPress={() => {
|
||||||
<List.Item
|
// Handle scanning QR code operation...
|
||||||
title="GitHub"
|
// closeOptions();
|
||||||
description="Linus"
|
}}
|
||||||
left={props => <Avatar.Image size={24} style={{marginLeft: '20px', backgroundColor: 'rgb(242,242,242)'}} source={'https://cdn.casbin.org/img/social_github.png'} />}
|
>
|
||||||
/>
|
<IconButton icon={'camera'} size={35} />
|
||||||
<List.Item
|
<Text style={{fontSize: 18}} >Scan QR code</Text>
|
||||||
title="Google"
|
</TouchableOpacity>
|
||||||
description="James Greenson"
|
<TouchableOpacity
|
||||||
left={props => <Avatar.Image size={24} style={{marginLeft: '20px', backgroundColor: 'rgb(242,242,242)'}} source={'https://cdn.casbin.org/img/social_google.png'} />}
|
style={{ flexDirection: 'row', alignItems: 'center', marginTop: 10 }}
|
||||||
/>
|
onPress={openEnterAccountModal}
|
||||||
<List.Item
|
>
|
||||||
title="Casdoor"
|
<IconButton icon={'keyboard'} size={35} />
|
||||||
description="admin"
|
<Text style={{fontSize: 18}}>Enter Secret code</Text>
|
||||||
left={props => <Avatar.Image size={24} style={{marginLeft: '20px', backgroundColor: 'rgb(242,242,242)'}} source={'https://cdn.casbin.org/img/social_casdoor.png'} />}
|
</TouchableOpacity>
|
||||||
/>
|
</Modal>
|
||||||
<List.Item
|
</Portal>
|
||||||
title="GitHub"
|
|
||||||
description="Linus"
|
<Portal>
|
||||||
left={props => <Avatar.Image size={24} style={{marginLeft: '20px', backgroundColor: 'rgb(242,242,242)'}} source={'https://cdn.casbin.org/img/social_github.png'} />}
|
<Modal
|
||||||
/>
|
visible={showEnterAccountModal}
|
||||||
<List.Item
|
onDismiss={closeEnterAccountModal}
|
||||||
title="Google"
|
contentContainerStyle={{
|
||||||
description="James Greenson"
|
backgroundColor: 'white',
|
||||||
left={props => <Avatar.Image size={24} style={{marginLeft: '20px', backgroundColor: 'rgb(242,242,242)'}} source={'https://cdn.casbin.org/img/social_google.png'} />}
|
padding: 1,
|
||||||
/>
|
borderRadius: 10,
|
||||||
</div>
|
width: '90%',
|
||||||
|
height: '40%',
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: [{ translateX: '-50%' }, { translateY: '-50%' }],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EnterAccountDetails onClose={closeEnterAccountModal} onAdd={handleAddAccount} />
|
||||||
|
</Modal>
|
||||||
|
</Portal>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: 30,
|
||||||
|
right: 30,
|
||||||
|
width: 70,
|
||||||
|
height: 70,
|
||||||
|
borderRadius: 35,
|
||||||
|
backgroundColor: '#393544',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
onPress={togglePlusButton}
|
||||||
|
>
|
||||||
|
<IconButton icon={isPlusButton ? 'plus' : 'close'} size={40} color={'white'} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,13 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Searchbar } from 'react-native-paper';
|
import { Searchbar } from 'react-native-paper';
|
||||||
|
|
||||||
const SearchBar = () => {
|
const SearchBar = ({ onSearch }) => {
|
||||||
const [searchQuery, setSearchQuery] = React.useState('');
|
const [searchQuery, setSearchQuery] = React.useState('');
|
||||||
|
|
||||||
const onChangeSearch = query => setSearchQuery(query);
|
const onChangeSearch = query => {
|
||||||
|
setSearchQuery(query);
|
||||||
|
onSearch(query);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Searchbar
|
<Searchbar
|
||||||
|
|
|
@ -11,8 +11,10 @@
|
||||||
"@expo/webpack-config": "^19.0.0",
|
"@expo/webpack-config": "^19.0.0",
|
||||||
"@react-navigation/bottom-tabs": "^6.5.8",
|
"@react-navigation/bottom-tabs": "^6.5.8",
|
||||||
"@react-navigation/native": "^6.1.7",
|
"@react-navigation/native": "^6.1.7",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
"expo": "~49.0.8",
|
"expo": "~49.0.8",
|
||||||
"expo-status-bar": "~1.6.0",
|
"expo-status-bar": "~1.6.0",
|
||||||
|
"hotp-totp": "^1.0.6",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-native": "0.72.4",
|
"react-native": "0.72.4",
|
||||||
|
@ -7182,6 +7184,15 @@
|
||||||
"readable-stream": "^3.4.0"
|
"readable-stream": "^3.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bl/node_modules/buffer": {
|
||||||
|
"version": "5.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||||
|
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"base64-js": "^1.3.1",
|
||||||
|
"ieee754": "^1.1.13"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bl/node_modules/readable-stream": {
|
"node_modules/bl/node_modules/readable-stream": {
|
||||||
"version": "3.6.2",
|
"version": "3.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||||
|
@ -7331,26 +7342,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/buffer": {
|
"node_modules/buffer": {
|
||||||
"version": "5.7.1",
|
"version": "6.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "github",
|
|
||||||
"url": "https://github.com/sponsors/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/feross"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "consulting",
|
|
||||||
"url": "https://feross.org/support"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"base64-js": "^1.3.1",
|
"base64-js": "^1.3.1",
|
||||||
"ieee754": "^1.1.13"
|
"ieee754": "^1.2.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/buffer-alloc": {
|
"node_modules/buffer-alloc": {
|
||||||
|
@ -10258,6 +10255,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||||
},
|
},
|
||||||
|
"node_modules/hotp-totp": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/hotp-totp/-/hotp-totp-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-+5nXaNkFF9YHuAjGo2VjLxPDVTf0QXZTVXBTGoS18eNb64ofQ25qnUINBPlDnmgriryM4UkPAGD2ATNCV9ivsg==",
|
||||||
|
"dependencies": {
|
||||||
|
"thirty-two": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hpack.js": {
|
"node_modules/hpack.js": {
|
||||||
"version": "2.1.6",
|
"version": "2.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
|
||||||
|
@ -16734,6 +16739,14 @@
|
||||||
"node": ">=0.8"
|
"node": ">=0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/thirty-two": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.2.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/throat": {
|
"node_modules/throat": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz",
|
||||||
|
|
|
@ -12,8 +12,10 @@
|
||||||
"@expo/webpack-config": "^19.0.0",
|
"@expo/webpack-config": "^19.0.0",
|
||||||
"@react-navigation/bottom-tabs": "^6.5.8",
|
"@react-navigation/bottom-tabs": "^6.5.8",
|
||||||
"@react-navigation/native": "^6.1.7",
|
"@react-navigation/native": "^6.1.7",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
"expo": "~49.0.8",
|
"expo": "~49.0.8",
|
||||||
"expo-status-bar": "~1.6.0",
|
"expo-status-bar": "~1.6.0",
|
||||||
|
"hotp-totp": "^1.0.6",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-native": "0.72.4",
|
"react-native": "0.72.4",
|
||||||
|
|
Loading…
Reference in New Issue