feat: add registration code generation
This commit is contained in:
parent
5ac154b741
commit
d869e8d119
@ -4,7 +4,6 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
|
||||||
|
|
||||||
gonanoid "github.com/matoous/go-nanoid"
|
gonanoid "github.com/matoous/go-nanoid"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
@ -31,11 +30,10 @@ func (db *DeviceDatabase) Initialize() error {
|
|||||||
return fmt.Errorf("error creating devices table: %s", error)
|
return fmt.Errorf("error creating devices table: %s", error)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, error = db.Connection.Exec(`CREATE TABLE IF NOT EXISTS sessions (
|
_, error = db.Connection.Exec(`CREATE TABLE IF NOT EXISTS integrations (
|
||||||
token TEXT PRIMARY KEY,
|
id TEXT PRIMARY KEY,
|
||||||
device_id INTEGER,
|
name TEXT UNIQUE,
|
||||||
expiry_date TIMESTAMP,
|
token TEXT
|
||||||
FOREIGN KEY (device_id) REFERENCES devices(id)
|
|
||||||
)`)
|
)`)
|
||||||
if error != nil {
|
if error != nil {
|
||||||
return fmt.Errorf("error creating device sessions table: %s", error)
|
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
|
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) {
|
func (db *DeviceDatabase) GetDeviceById(id string) (*PlaybackDevice, error) {
|
||||||
var device PlaybackDevice
|
var device PlaybackDevice
|
||||||
err := db.Connection.QueryRow("SELECT id, device_name, description FROM devices WHERE id = ?", id).Scan(&device.ID, &device.Name, &device.Description)
|
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
|
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) {
|
func (db *DeviceDatabase) GetDevices() (*[]PlaybackDevice, error) {
|
||||||
var devices []PlaybackDevice
|
var devices []PlaybackDevice
|
||||||
|
|
||||||
@ -136,6 +104,47 @@ func (db *DeviceDatabase) DeleteDevice(ID string) error {
|
|||||||
return err
|
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) {
|
func (db *DeviceDatabase) SetDirectory(directory string) {
|
||||||
db.databaseDirectory = directory
|
db.databaseDirectory = directory
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,10 +2,13 @@ package management
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
d "playback-device-server/data"
|
d "playback-device-server/data"
|
||||||
|
|
||||||
|
gonanoid "github.com/matoous/go-nanoid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DeviceManager struct {
|
type DeviceManager struct {
|
||||||
deviceDatabase *d.DeviceDatabase
|
deviceDatabase *d.DeviceDatabase
|
||||||
|
registrationCodes []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (um *DeviceManager) Initialize(deviceDatabase *d.DeviceDatabase) error {
|
func (um *DeviceManager) Initialize(deviceDatabase *d.DeviceDatabase) error {
|
||||||
@ -82,3 +85,9 @@ func (um *DeviceManager) DeleteDevice(ID string) error {
|
|||||||
error := um.deviceDatabase.DeleteDevice(ID)
|
error := um.deviceDatabase.DeleteDevice(ID)
|
||||||
return error
|
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) {
|
func (r *DeviceApiHandler) Initialize(authenticator *Authenticator) {
|
||||||
r.router.Use(authenticator.Authenticate("/api/devices", []string{}))
|
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("/register", r.handleRegister)
|
||||||
//usersApi.POST("/deregister", r.handleDeregister)
|
//usersApi.POST("/deregister", r.handleDeregister)
|
||||||
usersApi.GET("/session/info", r.handleGetSessionInfo)
|
devicesApi.GET("/session/info", r.handleGetSessionInfo)
|
||||||
usersApi.PUT("/:id", r.handleUpdateConfig)
|
devicesApi.PUT("/:id", r.handleUpdateConfig)
|
||||||
usersApi.GET("", r.handleGetDevices)
|
devicesApi.GET("", r.handleGetDevices)
|
||||||
usersApi.POST("", r.handleCreateDevice)
|
devicesApi.POST("", r.handleCreateDevice)
|
||||||
usersApi.DELETE("/:id", r.handleDeleteDevice)
|
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 {
|
//func (r DeviceApiHandler) handleRegister(context echo.Context) error {
|
||||||
|
|||||||
@ -19,12 +19,26 @@ function ModalHandler() {
|
|||||||
function setModalId(modalId) {
|
function setModalId(modalId) {
|
||||||
_modalId = modalId;
|
_modalId = modalId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onHidden(callback) {
|
||||||
|
_ref.addEventListener('hidden.bs.modal', () => {
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onShow(callback) {
|
||||||
|
_ref.addEventListener('show.bs.modal', () => {
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
setRef,
|
setRef,
|
||||||
show,
|
show,
|
||||||
hide,
|
hide,
|
||||||
setModalId,
|
setModalId,
|
||||||
|
onHidden,
|
||||||
|
onShow,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import CreateUserModal from "./create-user-modal.jsx";
|
|||||||
import DeleteUserModal from "./delete-user-modal.jsx";
|
import DeleteUserModal from "./delete-user-modal.jsx";
|
||||||
import UserSettingsModal from "./user-settings-modal.jsx";
|
import UserSettingsModal from "./user-settings-modal.jsx";
|
||||||
import CreateDeviceModal from "./create-device-modal.jsx";
|
import CreateDeviceModal from "./create-device-modal.jsx";
|
||||||
|
import ShowRegistrationCodeModal from "./show-registration-code-modal.jsx";
|
||||||
|
|
||||||
const ModalRegistry = (function () {
|
const ModalRegistry = (function () {
|
||||||
const modals = [
|
const modals = [
|
||||||
@ -27,6 +28,11 @@ const ModalRegistry = (function () {
|
|||||||
component: CreateDeviceModal,
|
component: CreateDeviceModal,
|
||||||
ref: null,
|
ref: null,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "showRegistrationCodeModal",
|
||||||
|
component: ShowRegistrationCodeModal,
|
||||||
|
ref: null,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function getModals(props) {
|
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 {
|
return {
|
||||||
getDevices,
|
getDevices,
|
||||||
createDevice,
|
createDevice,
|
||||||
updateDevice,
|
updateDevice,
|
||||||
deleteDevice,
|
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 List from "../modules/list";
|
||||||
import CreateDeviceModal from "../modals/create-device-modal";
|
import CreateDeviceModal from "../modals/create-device-modal";
|
||||||
import DeviceService from "../services/device-service";
|
import DeviceService from "../services/device-service";
|
||||||
|
import ShowRegistrationCodeModal from "../modals/show-registration-code-modal";
|
||||||
|
|
||||||
function DevicesView(props) {
|
function DevicesView(props) {
|
||||||
const DEVICES_LIST_TYPE = "devices";
|
const DEVICES_LIST_TYPE = "devices";
|
||||||
@ -27,6 +28,10 @@ function DevicesView(props) {
|
|||||||
let devices = await DeviceService.getDevices();
|
let devices = await DeviceService.getDevices();
|
||||||
setDevices(devices);
|
setDevices(devices);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleRegisterIntegration() {
|
||||||
|
ShowRegistrationCodeModal.Handler.show();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -63,44 +68,80 @@ function DevicesView(props) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-row justify-content-end flex-fill">
|
<div class="d-flex flex-row justify-content-end flex-fill">
|
||||||
<button class="btn btn-dark me-2 mb-3" onClick={handleNewDevice}>
|
<Show when={listType() === DEVICES_LIST_TYPE}>
|
||||||
<i class="bi bi-plus-square me-2"></i>
|
<button class="btn btn-dark me-2 mb-3" onClick={handleNewDevice}>
|
||||||
New Device
|
<i class="bi bi-plus-square me-2"></i>
|
||||||
</button>
|
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>
|
||||||
</div>
|
</div>
|
||||||
<List
|
<Show when={listType() === DEVICES_LIST_TYPE}>
|
||||||
onListItemClick={() => {}}
|
<List
|
||||||
items={devices().map((device) => ({
|
onListItemClick={() => {}}
|
||||||
id: {
|
items={devices().map((device) => ({
|
||||||
html: <span class="font-monospace">{device.getId()}</span>,
|
id: {
|
||||||
},
|
html: <span class="font-monospace">{device.getId()}</span>,
|
||||||
name: {
|
},
|
||||||
text: device.getName(),
|
name: {
|
||||||
},
|
text: device.getName(),
|
||||||
description: {
|
},
|
||||||
text: device.getDescription(),
|
description: {
|
||||||
},
|
text: device.getDescription(),
|
||||||
device,
|
},
|
||||||
}))}
|
device,
|
||||||
class={"flex-fill"}
|
}))}
|
||||||
columns={[
|
class={"flex-fill"}
|
||||||
{
|
columns={[
|
||||||
id: "id",
|
{
|
||||||
name: "id",
|
id: "id",
|
||||||
width: 6,
|
name: "id",
|
||||||
},
|
width: 6,
|
||||||
{
|
},
|
||||||
id: "name",
|
{
|
||||||
name: "Name",
|
id: "name",
|
||||||
width: 10,
|
name: "Name",
|
||||||
},
|
width: 10,
|
||||||
{
|
},
|
||||||
id: "description",
|
{
|
||||||
name: "Description",
|
id: "description",
|
||||||
},
|
name: "Description",
|
||||||
]}
|
},
|
||||||
></List>
|
]}
|
||||||
|
></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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user