Merge pull request #101 from IGx89/synology-ss/two-factor-auth

synology-ss: add 2FA support
This commit is contained in:
Koushik Dutta
2022-01-05 21:05:27 -08:00
committed by GitHub
4 changed files with 75 additions and 10 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/synology-ss",
"version": "0.0.4",
"version": "0.0.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/synology-ss",
"version": "0.0.4",
"version": "0.0.5",
"license": "Apache",
"dependencies": {
"axios": "^0.24.0"
@@ -17,7 +17,8 @@
}
},
"../../sdk": {
"version": "0.0.121",
"name": "@scrypted/sdk",
"version": "0.0.133",
"dev": true,
"license": "ISC",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/synology-ss",
"version": "0.0.4",
"version": "0.0.5",
"description": "A Synology Surveillance Station plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",

View File

@@ -36,7 +36,15 @@ export class SynologyApiClient {
idList: cameraIds.join(',')
};
return await this.sendRequest<SynologyCameraLiveViewPath[]>(params);
const errorCodeDescs = {
'400': 'Execution failed',
// Usually when 401 happens, there's a "Fail to get local host Ip str!" error in surveillance.log.
// One instance it was due to an old network bridge configured in Docker that had to be removed.
'401': 'Parameter invalid (possibly due to misconfigured Synology network interface -- run ifconfig on your server)',
'402': 'Camera disabled'
};
return await this.sendRequest<SynologyCameraLiveViewPath[]>(params, null, false, errorCodeDescs);
}
public async getCameraSnapshot(cameraId: number | string) {
@@ -67,7 +75,8 @@ export class SynologyApiClient {
return response.cameras;
}
public async login(account: string, password: string): Promise<void> {
public async login(account: string, password: string, otpCode?: number, enableDeviceToken: boolean = false, deviceName?: string,
deviceId?: string): Promise<string | undefined> {
const params = {
api: 'SYNO.API.Auth',
version: 6,
@@ -77,6 +86,22 @@ export class SynologyApiClient {
passwd: password
};
if (otpCode) {
params['otp_code'] = otpCode;
}
if (enableDeviceToken) {
params['enable_device_token'] = enableDeviceToken ? 'yes' : 'no';
}
if (deviceName) {
params['device_name'] = deviceName;
}
if (deviceId) {
params['device_id'] = deviceId;
}
const errorCodeDescs = {
'400': 'Invalid password',
'401': 'Guest or disabled account',
@@ -92,7 +117,9 @@ export class SynologyApiClient {
'411': 'Account Locked (when account max try exceed)'
};
await this.sendRequest(params, null, true, errorCodeDescs);
const response = await this.sendRequest<SynologyApiAuthResponse>(params, null, true, errorCodeDescs);
return response.did;
}
private async queryApiInfo(): Promise<Record<string, SynologyApiInfo>> {
@@ -153,6 +180,11 @@ interface SynologyApiResponse<T> {
success: boolean;
}
interface SynologyApiAuthResponse {
sid?: string;
did?: string;
}
interface SynologyApiListCamerasResponse {
total: number;
cameras: SynologyCamera[];

View File

@@ -211,6 +211,8 @@ class SynologySurveillanceStation extends ScryptedDeviceBase implements Settings
const url = this.getSetting('url');
const username = this.getSetting('username');
const password = this.getSetting('password');
const otpCode = this.getSetting('otpCode');
const mfaDeviceId = this.getSetting('mfaDeviceId');
this.log.clearAlerts();
@@ -234,8 +236,31 @@ class SynologySurveillanceStation extends ScryptedDeviceBase implements Settings
}
try {
await this.api.login(username, password);
const newMfaDeviceId = await this.api.login(username, password, otpCode ? parseInt(otpCode) : undefined, !!otpCode, 'Scrypted', mfaDeviceId);
// If a OTP was present, store the device ID to allow us to skip the OTP requirement next login.
if (otpCode) {
this.storage.setItem('mfaDeviceId', newMfaDeviceId);
}
}
catch (e) {
this.log.a(`login error: ${e}`);
this.console.error('login error', e);
// Clear device ID upon login failure, since it's likely useless now
this.storage.removeItem('mfaDeviceId');
return;
}
finally {
// Clear the OTP setting if provided since it's a temporary code
if (otpCode) {
this.storage.removeItem('otpCode');
this.onDeviceEvent(ScryptedInterface.Settings, undefined);
}
}
try {
this.cameras = await this.api.listCameras();
if (!this.cameras) {
@@ -289,8 +314,8 @@ class SynologySurveillanceStation extends ScryptedDeviceBase implements Settings
}
}
catch (e) {
this.log.a(`login error: ${e}`);
this.console.error('login error', e);
this.log.a(`device discovery error: ${e}`);
this.console.error('device discovery error', e);
}
}
@@ -323,6 +348,13 @@ class SynologySurveillanceStation extends ScryptedDeviceBase implements Settings
type: 'password',
value: this.getSetting('password'),
},
{
key: 'otpCode',
title: 'Verification Code (OTP)',
description: 'Required only if you have two-factor authentication enabled',
type: 'integer',
value: this.getSetting('otpCode'),
},
{
key: 'url',
title: 'Synology Surveillance Station URL',