upgrading auth functionality

This commit is contained in:
Andrew Gundersen 2021-03-23 10:24:06 -05:00
commit 9bf7496899
12 changed files with 92 additions and 362 deletions

View file

@ -15,8 +15,9 @@
/>
</span>
<!-- Windows -->
<router-view/>
<!-- Main Pages -->
<Messenger v-if="auth"/>
<Login v-else />
</div>
@ -26,7 +27,7 @@
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, ref } from 'vue';
import { defineComponent, onMounted, onUnmounted, ref } from "vue";
import { useIpc } from "@/modules/ipc";
import Splash from "@/components/splash.vue"
@ -34,33 +35,48 @@ export default defineComponent({
components: { Splash },
setup() {
// Show spash screen til true.
const ready = ref(false);
const { invoke } = useIpc();
// Whether user is logged in.
let auth = ref(false);
// Attempt token login
const key = window.localStorage.getItem(AUTH_KEY);
if (key) {
post("client-message", key)
}
// onAuthResponse
const onAuthResponse = (_event: IpcMainEvent, payload: Auth) => {
if (payload.token) {
auth.value = true;
}
}
// Post navbar action to backend.
const callNavbar = (action: string) => {
invoke('nav-bar', action);
post('nav-bar', action);
}
const onWindowReady = (_event: any, _payload: any) => {
ready.value = true;
}
onMounted(() => {
window.ipcRenderer.on("window-ready", onWindowReady);
window.ipcRenderer.on("auth-response", onAuthResponse)
window.ipcRenderer.on("window-ready", () => ready.value = true);
});
onUnmounted(() => {
window.ipcRenderer.removeAllListeners("window-ready")
window.ipcRenderer.removeAllListeners("auth-response")
})
return {
callNavbar,
ready
ready,
auth,
}
}
})

View file

@ -2,19 +2,24 @@
"use strict";
import { sendAudio } from './session'
import { ipcMain } from "electron";
import { backgroundMitt } from './emitter';
const portAudio = require('naudiodon');
// Audio in and out stream objects.
let ai: typeof portAudio.AudioIO | null = null;
let ao: typeof portAudio.AudioIO | null = null;
// Whether activly recording.
let record = false;
const audioContainer = {
input: '',
}
const encoding = "hex";
const audioOptions = {
channelCount: 1,
sampleFormat: 16,
@ -24,14 +29,15 @@ const audioOptions = {
}
// callback run on space bar key up and down.
const updateRecorder = (_event, payload: {
isRecording: boolean;
uid: string | null;
const updateRecorder = (_event, payload: { isRecording: boolean; uid: string | null;
}): void => {
record = payload.isRecording;
if (!record) {
if (payload.uid) sendAudio(audioContainer.input, payload.uid);
}
};
// listen for space key up/down event.
@ -53,7 +59,7 @@ function bufSplit(buf: Buffer, len: number): Array<Buffer> {
return chunks;
}
// main audio function run by run.ts module.
// Main audio function run by run.ts module.
export function initAudioIO(): void {
if (!ai) {
@ -79,7 +85,7 @@ export function initAudioIO(): void {
}
}
// play audio buffers.
// Audio playback.
export function play(input: Buffer): void {
let i = 0;
@ -122,6 +128,7 @@ export function play(input: Buffer): void {
}
}
// Get's called on window close.
export function stopStream() {
if(ai != null) {
ai.quit();

View file

@ -1,48 +1,24 @@
import { backgroundMitt } from './emitter';
import { backgroundMitt } from "./emitter";
import { renderMessage } from "@/modules/message";
import { Profile, StandardMessage, Annotation } from "@/types/message/index";
import { play } from "./audio";
// Attempt an authenticaion, either with Token or with Creds.
const onAuthRequest = async (e: any, payload: string | Creds) => {
return new Promise((resolve, reject) => {
console.log('Authenticating...');
// Handle auth response from server.
backgroundMitt.once('auth-res', (res: string) => {
if (res == "locked") {
reject(res);
} else {
console.log('Success!');
auth = true;
resolve(res);
}
});
if (typeof payload !== "string") {
payload = JSON.stringify(payload)
}
// Send token to backend
socket.send(payload);
export const handleAuthMessage = (message: string) => {
backgroundMitt.emit('ipc-renderer', {
endpoint: 'auth-response',
message: message
});
};
const handleAuthMessage = (message: string) => {
backgroundMitt.emit('auth-res', message);
}
const handleProfileMessage = (message: Profile) => {
export const handleProfileMessage = (message: Profile) => {
backgroundMitt.emit('ipc-renderer', {
endpoint: 'update-profile',
message: message
});
}
const handleStandardMessage = (m: StandardMessage) => {
export const handleStandardMessage = (m: StandardMessage) => {
// Create a render message object.
const message = renderMessage(
@ -61,7 +37,7 @@ const handleStandardMessage = (m: StandardMessage) => {
});
}
const handleAnnotationMessage = (message: Annotation) => {
export const handleAnnotation = (message: Annotation) => {
backgroundMitt.emit('ipc-renderer', {
endpoint: 'render-message',
message: message

View file

@ -1,27 +1,21 @@
"use strict";
import { createWindow } from './window';
import useWebSockets from './session';
import agentInterface from './session';
import { initAudioIO } from './audio';
let socket = false;
/*
* The main function will be run after electron app is ready.
*/
export async function main() {
const { initSession } = useWebSockets()
const { initSession } = agentInterface()
// create main window.
await createWindow();
// Instantiate socket session with crimata-platorm.
if (!socket) {
initSocketSession();
}
socket = true;
initSession();
// Begin audio stream.
initAudioIO();

View file

@ -1,14 +1,17 @@
import { backgroundMitt } from './emitter';
import { ipcMain, IpcMainEvent } from "electron";
import useWebSockets from "@/modules/ws";
import { handleAuthMessage, handleStandardMessage, handleProfileMessage, handleAnnotation } from "./handlers";
// Handle messages from server.
// Calls appropriate endpoint for a server message.
const onServerMessage = (data: string): void => {
const message = JSON.parse(data)
if (message.key) {
handleAuthMessage(message)
backgroundMitt.emit('auth-response', message);
}
else if (message.content) {
@ -20,27 +23,26 @@ const onServerMessage = (data: string): void => {
}
else {
handleAnnotationMessage(message)
handleAnnotation(message)
}
};
const { createSocket, sendMessage } = useWebSockets(onServerMessage);
// Handle messages from client.
const onClientMessage = (_event: IpcMainEvent, payload: any) {
sendMessage(JSON.stringify(payload))
}
// Routes messages to and from Crimata Servers.
export default function agentInterface() {
const { createSocket, sendMessage } = useWebSockets(onServerMessage)
const initSession = () => {
ipcMain.removeAllListeners()
ipcMain.removeHandler('auth-session');
ipcMain.handle('auth-request', onAuthRequest);
ipcMain.on('client-message', onClientMessage);
ipcMain.on("client-message", onClientMessage);
createSocket()
}
@ -49,117 +51,4 @@ export default function agentInterface() {
initSession
}
}
// Attempt an authenticaion, either with Token or with Creds.
const onAuthRequest = async (e: any, payload: string | Creds) => {
return new Promise((resolve, reject) => {
console.log('Authenticating...');
// Handle auth response from server.
backgroundMitt.once('auth-res', (res: string) => {
if (res == "locked") {
reject(res);
} else {
console.log('Success!');
auth = true;
resolve(res);
}
});
if (typeof payload !== "string") {
payload = JSON.stringify(payload)
}
// Send token to backend
socket.send(payload);
});
};
const handleAuthMessage = (message: string) => {
backgroundMitt.emit('auth-res', message);
}
const handleProfileMessage = (message: Profile) => {
backgroundMitt.emit('ipc-renderer', {
endpoint: 'update-profile',
message: message
});
}
const handleStandardMessage = (m: StandardMessage) => {
// Create a render message object.
const message = renderMessage(
m.content.text, m.content.audio, m.context, m.modifier);
// Play audio if any.
if (message.content.audio) {
const audioBytes = Buffer.from(m.content.audio as string, 'hex');
m.content.audio = true;
play(audioBytes);
}
backgroundMitt.emit('ipc-renderer', {
endpoint: 'render-message',
message: message
});
}
const handleAnnotationMessage = (message: Annotation) => {
backgroundMitt.emit('ipc-renderer', {
endpoint: 'render-message',
message: message
});
}
const onMessage = (messageStr: string): void => {
// Auth messages are strings.
if (!auth) {
handleAuthMessage(messageStr)
return
}
const message = JSON.parse(messageStr)
console.log("New message:")
console.log(message)
if (message.content) {
handleStandardMessage(message)
}
else if (message.first) {
handleProfileMessage(message)
}
else {
handleAnnotationMessage(message)
}
};
const onClientMessage = (e: any, payload: ClientMessage): void => {
console.log('sending message');
socket.send(JSON.stringify(payload));
}

View file

@ -72,7 +72,7 @@ export default function useAudioInputController (typing: Ref) {
const message = renderMessage("", "", "", "sf")
emitter.emit("self-message", message);
post('update-recorder', {
const audio = invoke('update-recorder', {
isRecording: false,
uid: message.uid
});

View file

@ -55,19 +55,17 @@
window.addEventListener("keydown", onEscape);
}
// When user clicks logout button in settings.
// We ask server to log us out.
const onLogout = () => {
logout();
router.push({ name: "login" });
post("logout", "")
}
post("auth-request", "logout")
}
return {
onActive,
toggleSettings,
onLogout
}
}
}
})
</script>

View file

@ -1,7 +1,6 @@
// src/main.ts
import App from "./App.vue";
import router from "./router";
import mitt from "mitt";
import { createApp } from "vue";
@ -12,6 +11,5 @@ const emitter = mitt();
const app = createApp(App)
app.use(router)
app.provide("mitt", emitter)
app.mount("#app");

View file

@ -21,7 +21,7 @@ if (token) {
window.localStorage.setItem(AUTH_KEY, token);
}
// Load key and invoke onAuthSession to try key-login.
// Token authentication.
const authToken = async () => {
console.log("Calling auth token!!")
const { invoke } = useIpc();

View file

@ -7,7 +7,7 @@ let socket: WebSocket;
// Run every time we want to connect to backend.
export default function useWebSockets(receiveCallback: (s: Buffer) => any) {
export default function useWebSockets(receiveCallback: (s: string) => any) {
const sendMessage = (data: string) => {
@ -27,14 +27,15 @@ export default function useWebSockets(receiveCallback: (s: Buffer) => any) {
console.log('Message received: ', event);
receiveCallback(event.data)
receiveCallback(event.data.toString())
}
const onClose = (event: WebSocket.CloseEvent) => {
console.log("socket closed normally.")
console.log("Socket closed normally.")
}
// Reconnect automatically on error.
const onError = (event: WebSocket.ErrorEvent) => {
console.log('WebSocket error: ', event);
@ -55,76 +56,8 @@ export default function useWebSockets(receiveCallback: (s: Buffer) => any) {
}
return {
createSocket
createSocket,
sendMessage
}
}
// Close existing sockets.
if (socket) {
socket.removeAllListeners();
socket.terminate();
socket.close();
success = false;
}
// Init new socket.
socket = new WebSocket(`ws://127.0.0.1:8760`);
socket.binaryType = 'arraybuffer';
// Handle requests for authentication (promise).
ipcMain.removeHandler('auth-session'); // avoid setting duplicate handlers
ipcMain.handle('auth-session', onAuthSession);
// handle user message event
ipcMain.removeAllListeners('client-message');
ipcMain.on('client-message', onClientMessage);
// handle renderer logout event
ipcMain.removeAllListeners('logout');
ipcMain.on('logout', (_event, _payload: string) => restart());
// Connect to the backend.
socket.on('open', () => {
console.log('Connected to Crimata Servers.');
// Try to authenticate token right away.
backgroundMitt.emit('ipc-renderer', {
endpoint: 'auth',
message: null
});
success = true;
});
// Error handling.
socket.on('error', (_e) => {
console.log('ERROR: Failed to connect.');
socket.removeAllListeners();
socket.close();
setTimeout(() => {
if (!success) {
console.log('Reconnecting...');
initSession();
}
}, 3000); // reconnect timeout
});
socket.on('close', () => {
console.log('Connection droped! Restarting...');
restart();
});
socket.on("message", onMessage);
}

View file

@ -1,55 +0,0 @@
import {
createRouter,
createWebHistory,
createWebHashHistory,
RouteRecordRaw
} from "vue-router";
import Home from "@/views/messenger.vue";
import { useAuth } from '@/modules/auth';
// Define the routes (/*) for the app here.
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "home",
component: Home,
meta: { requiresAuth: true },
},
{
path: "/login",
name: "login",
component: () => import("@/views/login.vue"),
meta: { requiresAuth: false },
},
{
path: "/register",
name: "register",
component: () => import("@/views/register.vue"),
meta: { requiresAuth: false },
},
];
const router = createRouter({
history: process.env.IS_ELECTRON ? createWebHashHistory() : createWebHistory(process.env.BASE_URL),
routes
});
// route auth check
router.beforeEach((to, from, next) => {
const { accessToken } = useAuth();
// Not logged into a guarded route?
if (to.meta.requiresAuth && !accessToken.value) {
next({ name: 'login' })
}
// Logged in for an auth route
else if ((to.name == 'login' || to.name == 'register') && accessToken.value){
next({ name: 'home' });
}
// Carry On...
else next();
})
export default router;

View file

@ -24,7 +24,7 @@
</div>
<!-- submit button; position: fixed -->
<button class="submitButton button" type="submit">Submit</button>
<button class="submitButton button" type="submitForm">Submit</button>
<div class="invalid" v-if="invalid">
Incorrect Credentials
@ -48,57 +48,31 @@ export default defineComponent({
setup() {
const { post } = useIpc();
const { setToken } = useAuth();
const { resetMessages } = useMessages();
const router = useRouter();
const email = ref("");
const password = ref("");
const invalid = ref(false);
const form = ref({
email: "",
password: "",
})
const { invoke } = useIpc();
const submit = async () => {
const payload = {
request: "login",
email: email.value,
password: password.value
};
try {
const response = await invoke('auth-session', payload);
// On login without token, we clear localStorage and messages.
window.localStorage.clear();
resetMessages();
setToken(response);
invalid.value = false;
router.push({ name: "home" });
}
catch(e) {
invalid.value = true;
console.log('Error loging in.');
}
};
const switchView = () => {
router.push({ name: "register" });
};
// Submit login credentials to the backend.
const submitForm = () => {
post("auth-message", form)
}
return {
submit,
email,
password,
switchView,
invalid
form,
submitForm
}
},
});
</script>
<style lang="scss" scoped>