enable logout from all devices in webapp

This commit is contained in:
Pierre Balzack 2023-06-26 17:06:55 -07:00
parent 11fdcf621f
commit b860494ca9
7 changed files with 82 additions and 50 deletions

View File

@ -913,30 +913,12 @@ paths:
required: true required: true
schema: schema:
type: string type: string
- name: appName - name: all
in: query in: query
description: name of connecting app description: whether all app tokens should be cleared
required: false required: false
schema: schema:
type: string type: boolean
- name: appVersion
in: query
description: version of connecting app
required: false
schema:
type: string
- name: platform
in: query
description: device platform
required: false
schema:
type: string
- name: deviceToken
in: query
description: deviceToken for push notification
required: false
schema:
type: string
responses: responses:
'200': '200':
description: ok description: ok

View File

@ -10,6 +10,9 @@ import (
//RemoveAgentToken //RemoveAgentToken
func RemoveAgentToken(w http.ResponseWriter, r *http.Request) { func RemoveAgentToken(w http.ResponseWriter, r *http.Request) {
// logout of all devices
logoutMode := r.FormValue("all") == "true"
// parse authentication token // parse authentication token
target, access, err := ParseToken(r.FormValue("agent")) target, access, err := ParseToken(r.FormValue("agent"))
if err != nil { if err != nil {
@ -17,30 +20,54 @@ func RemoveAgentToken(w http.ResponseWriter, r *http.Request) {
return return
} }
// load session if logoutMode {
var session store.Session var sessions []store.Session
if err = store.DB.Where("account_id = ? AND token = ?", target, access).Find(&session).Error; err != nil { if err = store.DB.Where("account_id = ?", target, access).Find(&sessions).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
ErrResponse(w, http.StatusNotFound, err);
} else {
ErrResponse(w, http.StatusInternalServerError, err); ErrResponse(w, http.StatusInternalServerError, err);
return;
} }
return;
}
// delete session // delete all sessions
err = store.DB.Transaction(func(tx *gorm.DB) error { err = store.DB.Transaction(func(tx *gorm.DB) error {
if res := tx.Where("session_id = ?", session.ID).Delete(&store.PushEvent{}).Error; res != nil { for _, session := range sessions {
return res if res := tx.Where("session_id = ?", session.ID).Delete(&store.PushEvent{}).Error; res != nil {
return res
}
if res := tx.Where("id = ?", session.ID).Delete(&store.Session{}).Error; res != nil {
return res
}
}
return nil
})
if err != nil {
ErrResponse(w, http.StatusInternalServerError, err)
return
} }
if res := tx.Where("id = ?", session.ID).Delete(&store.Session{}).Error; res != nil { } else {
return res var session store.Session
if err = store.DB.Where("account_id = ? AND token = ?", target, access).Find(&session).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
ErrResponse(w, http.StatusNotFound, err);
} else {
ErrResponse(w, http.StatusInternalServerError, err);
}
return;
}
// delete session
err = store.DB.Transaction(func(tx *gorm.DB) error {
if res := tx.Where("session_id = ?", session.ID).Delete(&store.PushEvent{}).Error; res != nil {
return res
}
if res := tx.Where("id = ?", session.ID).Delete(&store.Session{}).Error; res != nil {
return res
}
return nil
})
if err != nil {
ErrResponse(w, http.StatusInternalServerError, err)
return
} }
return nil
})
if err != nil {
ErrResponse(w, http.StatusInternalServerError, err)
return
} }
WriteResponse(w, nil) WriteResponse(w, nil)

View File

@ -1,7 +1,10 @@
import { checkResponse, fetchWithTimeout } from './fetchUtil'; import { checkResponse, fetchWithTimeout } from './fetchUtil';
export async function clearLogin(token) { export async function clearLogin(token, all) {
let logout = await fetchWithTimeout(`/account/apps?agent=${token}`, { method: 'DELETE' }) console.log("LOGOUT: ", token, all);
const param = all ? '&all=true' : ''
const logout = await fetchWithTimeout(`/account/apps?agent=${token}${param}`, { method: 'DELETE' })
checkResponse(logout) checkResponse(logout)
} }

View File

@ -71,8 +71,8 @@ export function useAppContext(websocket) {
} }
const actions = { const actions = {
logout: async () => { logout: async (all) => {
await appLogout(); await appLogout(all);
}, },
access: async (token) => { access: async (token) => {
await appAccess(token) await appAccess(token)
@ -140,10 +140,10 @@ export function useAppContext(websocket) {
return access.created; return access.created;
} }
const appLogout = async () => { const appLogout = async (all) => {
clearSession(); clearSession();
try { try {
await clearLogin(appToken.current); await clearLogin(appToken.current, all);
} }
catch (err) { catch (err) {
console.log(err); console.log(err);

View File

@ -1,6 +1,7 @@
import { Modal, Dropdown, Menu, Tooltip } from 'antd'; import { useRef } from 'react';
import { Modal, Switch, Dropdown, Menu, Tooltip } from 'antd';
import { Logo } from 'logo/Logo'; import { Logo } from 'logo/Logo';
import { IdentityWrapper, ErrorNotice, InfoNotice } from './Identity.styled'; import { IdentityWrapper, LogoutContent, ErrorNotice, InfoNotice } from './Identity.styled';
import { useIdentity } from './useIdentity.hook'; import { useIdentity } from './useIdentity.hook';
import { LogoutOutlined, InfoCircleOutlined, ExclamationCircleOutlined, DownOutlined } from '@ant-design/icons'; import { LogoutOutlined, InfoCircleOutlined, ExclamationCircleOutlined, DownOutlined } from '@ant-design/icons';
@ -8,14 +9,19 @@ export function Identity({ openAccount, openCards, cardUpdated }) {
const [modal, modalContext] = Modal.useModal(); const [modal, modalContext] = Modal.useModal();
const { state, actions } = useIdentity(); const { state, actions } = useIdentity();
const all = useRef(false);
const logout = () => { const logout = () => {
modal.confirm({ modal.confirm({
title: 'Are you sure you want to logout?', title: 'Are you sure you want to logout?',
icon: <LogoutOutlined />, icon: <LogoutOutlined />,
content: <LogoutContent onClick={(e) => e.stopPropagation()}>
<span className="logoutMode">Logout of All Devices </span>
<Switch onChange={(e) => {all.current = e}} size="small" />
</LogoutContent>,
bodyStyle: { padding: 16 }, bodyStyle: { padding: 16 },
onOk() { onOk() {
actions.logout(); actions.logout(all.current);
}, },
onCancel() {}, onCancel() {},
}); });

View File

@ -60,6 +60,18 @@ export const IdentityWrapper = styled.div`
} }
`; `;
export const LogoutContent = styled.div`
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
.logoutMode {
padding-right: 8px;
color: ${Colors.text};
}
`
export const ErrorNotice = styled.div` export const ErrorNotice = styled.div`
color: ${Colors.alert}; color: ${Colors.alert};
` `

View File

@ -33,7 +33,9 @@ export function useIdentity() {
}, [app.state]); }, [app.state]);
const actions = { const actions = {
logout: app.actions.logout, logout: (all) => {
app.actions.logout(all);
},
}; };
return { state, actions }; return { state, actions };