feat: add casdoor web login. (#6)

* feat: add casdoor web login.

* fix: remove clientSecret and redirectPath, add Alert box for empty input

* Update DefaultCasdoorSdkConfig.js

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
This commit is contained in:
ChenWenpeng 2023-09-27 20:58:04 +08:00 committed by GitHub
parent 3f927d14c6
commit a6c1d662d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 806 additions and 499 deletions

14
App.js
View File

@ -17,14 +17,24 @@ import {PaperProvider} from "react-native-paper";
import NavigationBar from "./NavigationBar"; import NavigationBar from "./NavigationBar";
import {NavigationContainer} from "@react-navigation/native"; import {NavigationContainer} from "@react-navigation/native";
import Header from "./Header"; import Header from "./Header";
import {UserProvider} from "./UserContext";
import {CasdoorServerProvider} from "./CasdoorServerContext";
export default function App() { const App = () => {
const [userInfo, setUserInfo] = React.useState(null);
const [casdoorServer, setCasdoorServer] = React.useState(null);
return ( return (
<CasdoorServerProvider value={{casdoorServer, setCasdoorServer}} >
<UserProvider value={{userInfo, setUserInfo}} >
<NavigationContainer> <NavigationContainer>
<PaperProvider> <PaperProvider>
<Header /> <Header />
<NavigationBar /> <NavigationBar />
</PaperProvider> </PaperProvider>
</NavigationContainer> </NavigationContainer>
</UserProvider>
</CasdoorServerProvider>
); );
} };
export default App;

80
CasdoorLoginPage.js Normal file
View File

@ -0,0 +1,80 @@
// 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, {useEffect} from "react";
import {WebView} from "react-native-webview";
import {View} from "react-native";
import {Portal} from "react-native-paper";
import SDK from "casdoor-react-native-sdk";
import UserContext from "./UserContext";
import PropTypes from "prop-types";
import EnterCasdoorSdkConfig from "./EnterCasdoorSdkConfig";
import CasdoorServerContext from "./CasdoorServerContext";
// import {LogBox} from "react-native";
// LogBox.ignoreAllLogs();
let sdk = null;
const CasdoorLoginPage = ({onWebviewClose}) => {
CasdoorLoginPage.propTypes = {
onWebviewClose: PropTypes.func.isRequired,
};
const [casdoorLoginURL, setCasdoorLoginURL] = React.useState("");
const {setUserInfo} = React.useContext(UserContext);
const [showConfigPage, setShowConfigPage] = React.useState(true);
const {casdoorServer} = React.useContext(CasdoorServerContext);
const handleHideConfigPage = () => {
setShowConfigPage(false);
};
const getCasdoorSignInUrl = async() => {
const signinUrl = await sdk.getSigninUrl();
setCasdoorLoginURL(signinUrl);
};
useEffect(() => {
if (casdoorServer) {
sdk = new SDK(casdoorServer);
getCasdoorSignInUrl();
}
}, [casdoorServer]);
const onNavigationStateChange = async(navState) => {
if (navState.url.startsWith(casdoorServer.redirectPath)) {
onWebviewClose();
const token = await sdk.getAccessToken(navState.url);
const userInfo = sdk.JwtDecode(token);
setUserInfo(userInfo);
}
};
return (
<Portal>
<View style={{flex: 1}}>
{showConfigPage && <EnterCasdoorSdkConfig onClose={handleHideConfigPage} onWebviewClose={onWebviewClose} />}
{!showConfigPage && casdoorLoginURL !== "" && (
<WebView
source={{uri: casdoorLoginURL}}
onNavigationStateChange={onNavigationStateChange}
style={{flex: 1}}
mixedContentMode="always"
javaScriptEnabled={true}
/>
)}
</View>
</Portal>
);
};
export const CasdoorLogout = () => {
sdk.clearState();
};
export default CasdoorLoginPage;

20
CasdoorServerContext.js Normal file
View File

@ -0,0 +1,20 @@
// 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 from "react";
const CasdoorServerContext = React.createContext();
export const CasdoorServerProvider = CasdoorServerContext.Provider;
export const CasdoorServerConsumer = CasdoorServerContext.Consumer;
export default CasdoorServerContext;

View File

@ -0,0 +1,23 @@
// 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.
const DefaultCasdoorSdkConfig = {
serverUrl: "https://door.casdoor.com",
clientId: "b800a86702dd4d29ec4d",
appName: "app-example",
organizationName: "casbin",
redirectPath: "http://casdoor-app",
signinPath: "/api/signin",
};
export default DefaultCasdoorSdkConfig;

View File

@ -38,6 +38,7 @@ export default function EnterAccountDetails({onClose, onEdit, placeholder}) {
placeholder={placeholder} placeholder={placeholder}
value={description} value={description}
onChangeText={(text) => setDescription(text)} onChangeText={(text) => setDescription(text)}
autoCapitalize="none"
style={{borderWidth: 3, borderColor: "white", margin: 10, width: 230, height: 50, borderRadius: 5, fontSize: 18, color: "gray", paddingLeft: 10}} style={{borderWidth: 3, borderColor: "white", margin: 10, width: 230, height: 50, borderRadius: 5, fontSize: 18, color: "gray", paddingLeft: 10}}
/> />
</View> </View>

View File

@ -48,8 +48,10 @@ export default function EnterAccountDetails({onClose, onAdd}) {
<View style={{flexDirection: "row", alignItems: "center"}}> <View style={{flexDirection: "row", alignItems: "center"}}>
<IconButton icon="account-details" size={35} /> <IconButton icon="account-details" size={35} />
<TextInput <TextInput
label="Description"
placeholder="Description" placeholder="Description"
value={description} value={description}
autoCapitalize="none"
onChangeText={(text) => setDescription(text)} onChangeText={(text) => setDescription(text)}
style={{borderWidth: 3, borderColor: "white", margin: 10, width: 230, height: 50, borderRadius: 5, fontSize: 18, color: "gray", paddingLeft: 10}} style={{borderWidth: 3, borderColor: "white", margin: 10, width: 230, height: 50, borderRadius: 5, fontSize: 18, color: "gray", paddingLeft: 10}}
/> />
@ -58,8 +60,10 @@ export default function EnterAccountDetails({onClose, onAdd}) {
<View style={{flexDirection: "row", alignItems: "center"}}> <View style={{flexDirection: "row", alignItems: "center"}}>
<IconButton icon="account-key" size={35} /> <IconButton icon="account-key" size={35} />
<TextInput <TextInput
label="Secret code"
placeholder="Secret code" placeholder="Secret code"
value={secretCode} value={secretCode}
autoCapitalize="none"
onChangeText={(text) => setSecretCode(text)} onChangeText={(text) => setSecretCode(text)}
secureTextEntry secureTextEntry
style={{borderWidth: 3, borderColor: "white", margin: 10, width: 230, height: 50, borderRadius: 5, fontSize: 18, color: "gray", paddingLeft: 10}} style={{borderWidth: 3, borderColor: "white", margin: 10, width: 230, height: 50, borderRadius: 5, fontSize: 18, color: "gray", paddingLeft: 10}}

176
EnterCasdoorSdkConfig.js Normal file
View File

@ -0,0 +1,176 @@
// 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 {Alert, Text, View} from "react-native";
import {Button, IconButton, Portal, TextInput} from "react-native-paper";
import DefaultCasdoorSdkConfig from "./DefaultCasdoorSdkConfig";
import CasdoorServerContext from "./CasdoorServerContext";
import PropTypes from "prop-types";
const EnterCasdoorSdkConfig = ({onClose, onWebviewClose}) => {
EnterCasdoorSdkConfig.propTypes = {
onClose: PropTypes.func.isRequired,
};
const {setCasdoorServer} = React.useContext(CasdoorServerContext);
const [CasdoorSdkConfig, setCasdoorSdkConfig] = useState({
serverUrl: "",
clientId: "",
appName: "",
organizationName: "",
redirectPath: "http://casdoor-app",
signinPath: "/api/signin",
});
const handleInputChange = (key, value) => {
setCasdoorSdkConfig({...CasdoorSdkConfig, [key]: value});
};
const closeConfigPage = () => {
onClose();
onWebviewClose();
};
const handleSave = () => {
if (
!CasdoorSdkConfig.serverUrl ||
!CasdoorSdkConfig.clientId ||
!CasdoorSdkConfig.appName ||
!CasdoorSdkConfig.organizationName ||
!CasdoorSdkConfig.redirectPath
) {
Alert.alert("Please fill in all the fields!");
return;
}
setCasdoorServer(CasdoorSdkConfig);
onClose();
};
const handleUseDefault = () => {
setCasdoorServer(DefaultCasdoorSdkConfig);
onClose();
};
return (
<Portal>
<View style={{flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: "white"}}>
<View style={{top: -60, flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: "white"}}>
<Text style={{fontSize: 24, marginBottom: 5}}>Casdoor server</Text>
<TextInput
label="Endpoint"
value={CasdoorSdkConfig.serverUrl}
onChangeText={(text) => handleInputChange("serverUrl", text)}
autoCapitalize="none"
style={{
borderWidth: 3,
borderColor: "white",
margin: 10,
width: 300,
height: 50,
borderRadius: 5,
fontSize: 18,
color: "gray",
paddingLeft: 10,
}}
/>
<TextInput
label="ClientID"
value={CasdoorSdkConfig.clientId}
onChangeText={(text) => handleInputChange("clientId", text)}
autoCapitalize="none"
style={{
borderWidth: 3,
borderColor: "white",
margin: 10,
width: 300,
height: 50,
borderRadius: 5,
fontSize: 18,
color: "gray",
paddingLeft: 10,
}}
/>
<TextInput
label="appName"
value={CasdoorSdkConfig.appName}
onChangeText={(text) => handleInputChange("appName", text)}
autoCapitalize="none"
style={{
borderWidth: 3,
borderColor: "white",
margin: 10,
width: 300,
height: 50,
borderRadius: 5,
fontSize: 18,
color: "gray",
paddingLeft: 10,
}}
/>
<TextInput
label="organizationName"
value={CasdoorSdkConfig.organizationName}
onChangeText={(text) => handleInputChange("organizationName", text)}
autoCapitalize="none"
style={{
borderWidth: 3,
borderColor: "white",
margin: 10,
width: 300,
height: 50,
borderRadius: 5,
fontSize: 18,
color: "gray",
paddingLeft: 10,
}}
/>
<Button
mode="contained"
onPress={handleSave}
style={{
backgroundColor: "#E6DFF3",
borderRadius: 5,
margin: 10,
alignItems: "center",
position: "absolute",
top: 600,
width: 300,
height: 50,
display: "flex",
justifyContent: "center",
}}
>
<Text style={{fontSize: 21, width: 300, color: "black"}}>Confirm</Text>
</Button>
<Button
mode="contained"
onPress={handleUseDefault}
style={{
backgroundColor: "#E6DFF3",
borderRadius: 5,
margin: 10,
alignItems: "center",
position: "absolute",
top: 660,
width: 300,
}}
>
<Text style={{fontSize: 18, width: 300, color: "black"}}>Use Casdoor Demo Site</Text>
</Button>
<IconButton icon={"close"} size={30} onPress={closeConfigPage} style={{position: "absolute", top: 120, right: -30}} />
</View>
</View>
</Portal>
);
};
export default EnterCasdoorSdkConfig;

View File

@ -13,14 +13,65 @@
// limitations under the License. // limitations under the License.
import * as React from "react"; import * as React from "react";
import {Appbar, Avatar, Text} from "react-native-paper"; import {Appbar, Avatar, Button, Menu, Text} from "react-native-paper";
import UserContext from "./UserContext";
import {View} from "react-native";
import CasdoorLoginPage, {CasdoorLogout} from "./CasdoorLoginPage";
const Header = () => ( const Header = () => {
const {userInfo, setUserInfo} = React.useContext(UserContext);
const [showLoginPage, setShowLoginPage] = React.useState(false);
const [menuVisible, setMenuVisible] = React.useState(false);
const openMenu = () => setMenuVisible(true);
const closeMenu = () => setMenuVisible(false);
const handleMenuLogoutClicked = () => {
handleCasdoorLogout();
closeMenu();
};
const handleCasdoorLogin = () => {
setShowLoginPage(true);
};
const handleCasdoorLogout = () => {
CasdoorLogout();
setUserInfo(null);
};
const handleHideLoginPage = () => {
setShowLoginPage(false);
};
return (
<View>
<Appbar.Header style={{height: 40}}> <Appbar.Header style={{height: 40}}>
<Appbar.Content title="Casdoor" /> <Appbar.Content title="Casdoor" />
<Avatar.Image size={32} source={{uri: "https://cdn.casbin.org/img/social_casdoor.png"}} style={{marginRight: 10, backgroundColor: "transparent"}} /> <Menu
<Text style={{marginRight: 10}} variant="titleMedium">Admin</Text> visible={menuVisible}
anchor={
<Button
style={{marginRight: 10, backgroundColor: "transparent", height: 40}}
onPress={userInfo === null ? handleCasdoorLogin : openMenu}
>
{
userInfo === null ?
null :
<Avatar.Image
size={32}
source={{uri: userInfo.avatar}}
style={{marginRight: 10, backgroundColor: "transparent"}}
/>
}
<Text style={{marginRight: 10}} variant="titleMedium">
{userInfo === null ? "Login" : userInfo.name}
</Text>
</Button>
}
onDismiss={closeMenu}
>
<Menu.Item onPress={() => handleMenuLogoutClicked()} title="Logout" />
</Menu>
</Appbar.Header> </Appbar.Header>
{showLoginPage && <CasdoorLoginPage onWebviewClose={handleHideLoginPage} />}
</View>
); );
};
export default Header; export default Header;

View File

@ -15,13 +15,36 @@
import * as React from "react"; import * as React from "react";
import {Button} from "react-native-paper"; import {Button} from "react-native-paper";
import {View} from "react-native"; import {View} from "react-native";
import CasdoorLoginPage, {CasdoorLogout} from "./CasdoorLoginPage";
import UserContext from "./UserContext";
const SettingPage = () => {
const [showLoginPage, setShowLoginPage] = React.useState(false);
const {userInfo, setUserInfo} = React.useContext(UserContext);
const handleCasdoorLogin = () => {
setShowLoginPage(true);
};
const handleCasdoorLogout = () => {
CasdoorLogout();
setUserInfo(null);
};
const handleHideLoginPage = () => {
setShowLoginPage(false);
};
export default function SettingPage() {
return ( return (
<View> <View>
<Button style={{marginTop: "50%", marginLeft: "20%", marginRight: "20%"}} icon="login" mode="contained" onPress={() => {}}> <Button
Login with Casdoor style={{marginTop: "50%", marginLeft: "20%", marginRight: "20%"}}
icon={userInfo === null ? "login" : "logout"}
mode="contained"
onPress={userInfo === null ? handleCasdoorLogin : handleCasdoorLogout}
>
{userInfo === null ? "Login with Casdoor" : "Logout"}
</Button> </Button>
{showLoginPage && <CasdoorLoginPage onWebviewClose={handleHideLoginPage} />}
</View> </View>
); );
} };
export default SettingPage;

20
UserContext.js Normal file
View File

@ -0,0 +1,20 @@
// 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 from "react";
const UserContext = React.createContext();
export const UserProvider = UserContext.Provider;
export const UserConsumer = UserContext.Consumer;
export default UserContext;

851
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@
"@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",
"casdoor-react-native-sdk": "^1.1.0",
"eslint-plugin-import": "^2.28.1", "eslint-plugin-import": "^2.28.1",
"expo": "~49.0.8", "expo": "~49.0.8",
"expo-barcode-scanner": "^12.5.3", "expo-barcode-scanner": "^12.5.3",
@ -20,16 +21,19 @@
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"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.5",
"react-native-gesture-handler": "^2.12.1", "react-native-gesture-handler": "^2.12.1",
"react-native-paper": "^5.10.3", "react-native-paper": "^5.10.3",
"react-native-web": "~0.19.6", "react-native-web": "~0.19.6",
"react-native-webview": "13.2.2",
"totp-generator": "^0.0.14" "totp-generator": "^0.0.14"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.20.0", "@babel/core": "^7.20.0",
"@babel/eslint-parser": "^7.18.9", "@babel/eslint-parser": "^7.18.9",
"@babel/preset-react": "^7.18.6", "@babel/preset-react": "^7.18.6",
"@types/react": "~18.2.14",
"@types/react-native": "^0.72.2",
"@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/eslint-plugin": "^5.62.0",
"eslint": "8.22.0", "eslint": "8.22.0",
"eslint-import-resolver-babel-module": "^5.3.2", "eslint-import-resolver-babel-module": "^5.3.2",