feat: add registration code generation
This commit is contained in:
parent
5ac154b741
commit
d869e8d119
@ -4,7 +4,6 @@ import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
gonanoid "github.com/matoous/go-nanoid"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
@ -31,11 +30,10 @@ func (db *DeviceDatabase) Initialize() error {
|
||||
return fmt.Errorf("error creating devices table: %s", error)
|
||||
}
|
||||
|
||||
_, error = db.Connection.Exec(`CREATE TABLE IF NOT EXISTS sessions (
|
||||
token TEXT PRIMARY KEY,
|
||||
device_id INTEGER,
|
||||
expiry_date TIMESTAMP,
|
||||
FOREIGN KEY (device_id) REFERENCES devices(id)
|
||||
_, error = db.Connection.Exec(`CREATE TABLE IF NOT EXISTS integrations (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT UNIQUE,
|
||||
token TEXT
|
||||
)`)
|
||||
if error != nil {
|
||||
return fmt.Errorf("error creating device sessions table: %s", error)
|
||||
@ -55,18 +53,6 @@ func (db *DeviceDatabase) CreateDevice(device_name string, description string) (
|
||||
return deviceID, err
|
||||
}
|
||||
|
||||
func (db *DeviceDatabase) CreateSession(deviceID string, expiryDate time.Time) (string, error) {
|
||||
sessionToken, err := gonanoid.Nanoid(16)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_, err = db.Connection.Exec("INSERT INTO sessions (device_id, token, expiry_date) VALUES (?, ?, ?)", deviceID, sessionToken, expiryDate)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sessionToken, nil
|
||||
}
|
||||
|
||||
func (db *DeviceDatabase) GetDeviceById(id string) (*PlaybackDevice, error) {
|
||||
var device PlaybackDevice
|
||||
err := db.Connection.QueryRow("SELECT id, device_name, description FROM devices WHERE id = ?", id).Scan(&device.ID, &device.Name, &device.Description)
|
||||
@ -90,24 +76,6 @@ func (db *DeviceDatabase) UpdateDevice(device *PlaybackDevice) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DeviceDatabase) GetSession(sessionToken string) (*DeviceSession, error) {
|
||||
var session DeviceSession
|
||||
row := db.Connection.QueryRow("SELECT token, device_id, expiry_date FROM sessions WHERE token = ?", sessionToken)
|
||||
err := row.Scan(&session.Token, &session.DeviceID, &session.ExpiryDate)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &session, nil
|
||||
}
|
||||
|
||||
func (db *DeviceDatabase) DeleteSessionByToken(token string) error {
|
||||
_, err := db.Connection.Exec("DELETE FROM sessions WHERE token = ?", token)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DeviceDatabase) GetDevices() (*[]PlaybackDevice, error) {
|
||||
var devices []PlaybackDevice
|
||||
|
||||
@ -136,6 +104,47 @@ func (db *DeviceDatabase) DeleteDevice(ID string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DeviceDatabase) CreateIntegration(name string) (string, string, error) {
|
||||
id, err := gonanoid.Nanoid(10)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
token, err := gonanoid.Nanoid(16)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
hashed_token, err := hashPassword(token)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
_, err = db.Connection.Exec("INSERT INTO integrations (id, name, token) VALUES (?, ?, ?)", id, name, hashed_token)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return id, token, nil
|
||||
}
|
||||
|
||||
func (db *DeviceDatabase) GetSession(sessionToken string) (*DeviceSession, error) {
|
||||
var session DeviceSession
|
||||
row := db.Connection.QueryRow("SELECT token, device_id, expiry_date FROM sessions WHERE token = ?", sessionToken)
|
||||
err := row.Scan(&session.Token, &session.DeviceID, &session.ExpiryDate)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &session, nil
|
||||
}
|
||||
|
||||
func (db *DeviceDatabase) DeleteSessionByToken(token string) error {
|
||||
_, err := db.Connection.Exec("DELETE FROM sessions WHERE token = ?", token)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DeviceDatabase) SetDirectory(directory string) {
|
||||
db.databaseDirectory = directory
|
||||
}
|
||||
|
||||
@ -2,10 +2,13 @@ package management
|
||||
|
||||
import (
|
||||
d "playback-device-server/data"
|
||||
|
||||
gonanoid "github.com/matoous/go-nanoid"
|
||||
)
|
||||
|
||||
type DeviceManager struct {
|
||||
deviceDatabase *d.DeviceDatabase
|
||||
deviceDatabase *d.DeviceDatabase
|
||||
registrationCodes []string
|
||||
}
|
||||
|
||||
func (um *DeviceManager) Initialize(deviceDatabase *d.DeviceDatabase) error {
|
||||
@ -82,3 +85,9 @@ func (um *DeviceManager) DeleteDevice(ID string) error {
|
||||
error := um.deviceDatabase.DeleteDevice(ID)
|
||||
return error
|
||||
}
|
||||
|
||||
func (um *DeviceManager) GetRegistrationCode() (string, error) {
|
||||
code := gonanoid.MustGenerate("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 6)
|
||||
um.registrationCodes = append(um.registrationCodes, code)
|
||||
return code, nil
|
||||
}
|
||||
|
||||
@ -15,14 +15,33 @@ type DeviceApiHandler struct {
|
||||
|
||||
func (r *DeviceApiHandler) Initialize(authenticator *Authenticator) {
|
||||
r.router.Use(authenticator.Authenticate("/api/devices", []string{}))
|
||||
usersApi := r.router.Group("/api/devices")
|
||||
devicesApi := r.router.Group("/api/devices")
|
||||
//usersApi.POST("/register", r.handleRegister)
|
||||
//usersApi.POST("/deregister", r.handleDeregister)
|
||||
usersApi.GET("/session/info", r.handleGetSessionInfo)
|
||||
usersApi.PUT("/:id", r.handleUpdateConfig)
|
||||
usersApi.GET("", r.handleGetDevices)
|
||||
usersApi.POST("", r.handleCreateDevice)
|
||||
usersApi.DELETE("/:id", r.handleDeleteDevice)
|
||||
devicesApi.GET("/session/info", r.handleGetSessionInfo)
|
||||
devicesApi.PUT("/:id", r.handleUpdateConfig)
|
||||
devicesApi.GET("", r.handleGetDevices)
|
||||
devicesApi.POST("", r.handleCreateDevice)
|
||||
devicesApi.DELETE("/:id", r.handleDeleteDevice)
|
||||
integrationsApi := r.router.Group("/api/integrations")
|
||||
integrationsApi.GET("/register", r.handleIntegrationRegistration)
|
||||
}
|
||||
|
||||
func (r *DeviceApiHandler) handleIntegrationRegistration(context echo.Context) error {
|
||||
code, error := r.deviceManager.GetRegistrationCode()
|
||||
|
||||
if error != nil {
|
||||
SendError(500, context, fmt.Sprintf("failed to get registration code: %s", error))
|
||||
return error
|
||||
}
|
||||
|
||||
response := struct {
|
||||
Code string `json:"code"`
|
||||
}{
|
||||
Code: code,
|
||||
}
|
||||
|
||||
return context.JSON(200, response)
|
||||
}
|
||||
|
||||
//func (r DeviceApiHandler) handleRegister(context echo.Context) error {
|
||||
|
||||
@ -19,12 +19,26 @@ function ModalHandler() {
|
||||
function setModalId(modalId) {
|
||||
_modalId = modalId;
|
||||
}
|
||||
|
||||
function onHidden(callback) {
|
||||
_ref.addEventListener('hidden.bs.modal', () => {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function onShow(callback) {
|
||||
_ref.addEventListener('show.bs.modal', () => {
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
setRef,
|
||||
show,
|
||||
hide,
|
||||
setModalId,
|
||||
onHidden,
|
||||
onShow,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import CreateUserModal from "./create-user-modal.jsx";
|
||||
import DeleteUserModal from "./delete-user-modal.jsx";
|
||||
import UserSettingsModal from "./user-settings-modal.jsx";
|
||||
import CreateDeviceModal from "./create-device-modal.jsx";
|
||||
import ShowRegistrationCodeModal from "./show-registration-code-modal.jsx";
|
||||
|
||||
const ModalRegistry = (function () {
|
||||
const modals = [
|
||||
@ -27,6 +28,11 @@ const ModalRegistry = (function () {
|
||||
component: CreateDeviceModal,
|
||||
ref: null,
|
||||
},
|
||||
{
|
||||
id: "showRegistrationCodeModal",
|
||||
component: ShowRegistrationCodeModal,
|
||||
ref: null,
|
||||
},
|
||||
];
|
||||
|
||||
function getModals(props) {
|
||||
|
||||
66
www/src/modals/show-registration-code-modal.jsx
Normal file
66
www/src/modals/show-registration-code-modal.jsx
Normal file
@ -0,0 +1,66 @@
|
||||
import {
|
||||
createEffect,
|
||||
createSignal,
|
||||
Show,
|
||||
createResource,
|
||||
onMount,
|
||||
} from "solid-js";
|
||||
import Modal from "./modal.jsx";
|
||||
import ModalHandler from "./modal-handler.js";
|
||||
import DeviceService from "../services/device-service.js";
|
||||
|
||||
function ShowRegistrationCodeModal(props) {
|
||||
const [error, setError] = createSignal("");
|
||||
const [code, { mutate, refetch }] = createResource(async () =>
|
||||
DeviceService.getRegistrationCode()
|
||||
);
|
||||
|
||||
onMount(() => {
|
||||
ShowRegistrationCodeModal.Handler.onHidden(() => mutate(null));
|
||||
ShowRegistrationCodeModal.Handler.onShow(() => refetch());
|
||||
});
|
||||
|
||||
function handleClose() {
|
||||
ShowRegistrationCodeModal.Handler.hide();
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
ref={props.ref}
|
||||
id="showRegistrationCodeModal"
|
||||
modalTitle="Register"
|
||||
centered={true}
|
||||
>
|
||||
<div class="modal-body">
|
||||
<Show when={error() !== ""}>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{error()}
|
||||
</div>
|
||||
</Show>
|
||||
<div class="mb-3 row">
|
||||
<label for="new_name" class="col-form-label col-sm-4">
|
||||
Enter this code on the device:
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
<input
|
||||
class="form-control font-monospace"
|
||||
type="text"
|
||||
id="new_name"
|
||||
value={code()}
|
||||
readonly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" onClick={handleClose} class="btn btn-primary">
|
||||
OK
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
ShowRegistrationCodeModal.Handler = new ModalHandler();
|
||||
|
||||
export default ShowRegistrationCodeModal;
|
||||
@ -63,11 +63,27 @@ function DeviceService() {
|
||||
}
|
||||
}
|
||||
|
||||
async function getRegistrationCode() {
|
||||
let response = await Net.sendRequest({
|
||||
method: "GET",
|
||||
url: "/api/integrations/register",
|
||||
});
|
||||
|
||||
if (response.status !== 200) {
|
||||
let responseData = JSON.parse(response.data);
|
||||
throw new Error(responseData.error);
|
||||
}
|
||||
|
||||
let codeObject = JSON.parse(response.data);
|
||||
return codeObject.code;
|
||||
}
|
||||
|
||||
return {
|
||||
getDevices,
|
||||
createDevice,
|
||||
updateDevice,
|
||||
deleteDevice,
|
||||
getRegistrationCode,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { createSignal, onMount } from "solid-js";
|
||||
import { createSignal, onMount, Show } from "solid-js";
|
||||
|
||||
import List from "../modules/list";
|
||||
import CreateDeviceModal from "../modals/create-device-modal";
|
||||
import DeviceService from "../services/device-service";
|
||||
import ShowRegistrationCodeModal from "../modals/show-registration-code-modal";
|
||||
|
||||
function DevicesView(props) {
|
||||
const DEVICES_LIST_TYPE = "devices";
|
||||
@ -27,6 +28,10 @@ function DevicesView(props) {
|
||||
let devices = await DeviceService.getDevices();
|
||||
setDevices(devices);
|
||||
}
|
||||
|
||||
function handleRegisterIntegration() {
|
||||
ShowRegistrationCodeModal.Handler.show();
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -63,44 +68,80 @@ function DevicesView(props) {
|
||||
</button>
|
||||
</div>
|
||||
<div class="d-flex flex-row justify-content-end flex-fill">
|
||||
<button class="btn btn-dark me-2 mb-3" onClick={handleNewDevice}>
|
||||
<i class="bi bi-plus-square me-2"></i>
|
||||
New Device
|
||||
</button>
|
||||
<Show when={listType() === DEVICES_LIST_TYPE}>
|
||||
<button class="btn btn-dark me-2 mb-3" onClick={handleNewDevice}>
|
||||
<i class="bi bi-plus-square me-2"></i>
|
||||
New Device
|
||||
</button>
|
||||
</Show>
|
||||
<Show when={listType() === INTEGRATION_LIST_TYPE}>
|
||||
<button class="btn btn-dark me-2 mb-3" onClick={handleRegisterIntegration}>
|
||||
<i class="bi bi-plus-square me-2"></i>
|
||||
Register
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<List
|
||||
onListItemClick={() => {}}
|
||||
items={devices().map((device) => ({
|
||||
id: {
|
||||
html: <span class="font-monospace">{device.getId()}</span>,
|
||||
},
|
||||
name: {
|
||||
text: device.getName(),
|
||||
},
|
||||
description: {
|
||||
text: device.getDescription(),
|
||||
},
|
||||
device,
|
||||
}))}
|
||||
class={"flex-fill"}
|
||||
columns={[
|
||||
{
|
||||
id: "id",
|
||||
name: "id",
|
||||
width: 6,
|
||||
},
|
||||
{
|
||||
id: "name",
|
||||
name: "Name",
|
||||
width: 10,
|
||||
},
|
||||
{
|
||||
id: "description",
|
||||
name: "Description",
|
||||
},
|
||||
]}
|
||||
></List>
|
||||
<Show when={listType() === DEVICES_LIST_TYPE}>
|
||||
<List
|
||||
onListItemClick={() => {}}
|
||||
items={devices().map((device) => ({
|
||||
id: {
|
||||
html: <span class="font-monospace">{device.getId()}</span>,
|
||||
},
|
||||
name: {
|
||||
text: device.getName(),
|
||||
},
|
||||
description: {
|
||||
text: device.getDescription(),
|
||||
},
|
||||
device,
|
||||
}))}
|
||||
class={"flex-fill"}
|
||||
columns={[
|
||||
{
|
||||
id: "id",
|
||||
name: "id",
|
||||
width: 6,
|
||||
},
|
||||
{
|
||||
id: "name",
|
||||
name: "Name",
|
||||
width: 10,
|
||||
},
|
||||
{
|
||||
id: "description",
|
||||
name: "Description",
|
||||
},
|
||||
]}
|
||||
></List>
|
||||
</Show>
|
||||
<Show when={listType() === INTEGRATION_LIST_TYPE}>
|
||||
<List
|
||||
onListItemClick={() => {}}
|
||||
items={[].map((integration) => ({
|
||||
id: {
|
||||
html: <span class="font-monospace">{integration.getId()}</span>,
|
||||
},
|
||||
name: {
|
||||
text: integration.getName(),
|
||||
},
|
||||
integration: integration,
|
||||
}))}
|
||||
class={"flex-fill"}
|
||||
columns={[
|
||||
{
|
||||
id: "id",
|
||||
name: "id",
|
||||
width: 6,
|
||||
},
|
||||
{
|
||||
id: "name",
|
||||
name: "Name",
|
||||
},
|
||||
]}
|
||||
></List>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user