mirror of
https://github.com/koush/scrypted.git
synced 2026-02-03 14:13:28 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a4336879c | ||
|
|
e5cef3f217 | ||
|
|
d34396afbc | ||
|
|
2622fc9256 | ||
|
|
410b1a4813 | ||
|
|
403c742be3 | ||
|
|
50a471b78f | ||
|
|
9b7ead26e0 | ||
|
|
3127bc38cb | ||
|
|
fb8b1a893d | ||
|
|
779d8eaa42 |
@@ -153,10 +153,17 @@ export async function loginScryptedClient(options: ScryptedLoginOptions) {
|
||||
|
||||
export async function checkScryptedClientLogin(options?: ScryptedConnectionOptions) {
|
||||
let { baseUrl } = options || {};
|
||||
const url = combineBaseUrl(baseUrl, 'login');
|
||||
let url = combineBaseUrl(baseUrl, 'login');
|
||||
const headers: AxiosRequestHeaders = {};
|
||||
if (options?.previousLoginResult?.authorization)
|
||||
headers.Authorization = options?.previousLoginResult?.authorization;
|
||||
if (options?.previousLoginResult?.queryToken) {
|
||||
// headers.Authorization = options?.previousLoginResult?.authorization;
|
||||
// const search = new URLSearchParams(options.previousLoginResult.queryToken);
|
||||
// url += '?' + search.toString();
|
||||
const token = options?.previousLoginResult.username + ":" + options.previousLoginResult.token;
|
||||
const hash = Buffer.from(token).toString('base64');
|
||||
|
||||
headers.Authorization = `Basic ${hash}`;
|
||||
}
|
||||
const response = await axios.get(url, {
|
||||
withCredentials: true,
|
||||
headers,
|
||||
@@ -185,6 +192,8 @@ export async function checkScryptedClientLogin(options?: ScryptedConnectionOptio
|
||||
}
|
||||
|
||||
export interface ScryptedClientLoginResult {
|
||||
username: string;
|
||||
token: string;
|
||||
authorization: string;
|
||||
queryToken: { [parameter: string]: string };
|
||||
localAddresses: string[];
|
||||
@@ -235,11 +244,19 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
|
||||
let scryptedCloud: boolean;
|
||||
let directAddress: string;
|
||||
let cloudAddress: string;
|
||||
let token: string;
|
||||
|
||||
console.log('@scrypted/client', packageJson.version);
|
||||
|
||||
const extraHeaders: { [header: string]: string } = {};
|
||||
|
||||
// Chrome will complain about websites making xhr requests to self signed https sites, even
|
||||
// if the cert has been accepted. Other browsers seem fine.
|
||||
// So the default is not to connect to IP addresses on Chrome, but do so on other browsers.
|
||||
const isChrome = globalThis.navigator?.userAgent.includes('Chrome');
|
||||
const isNotChromeOrIsInstalledApp = !isChrome || isInstalledApp();
|
||||
let tryAlternateAddresses = false;
|
||||
|
||||
if (username && password) {
|
||||
const loginResult = await loginScryptedClient(options as ScryptedLoginOptions);
|
||||
if (loginResult.authorization)
|
||||
@@ -250,6 +267,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
|
||||
cloudAddress = loginResult.cloudAddress;
|
||||
authorization = loginResult.authorization;
|
||||
queryToken = loginResult.queryToken;
|
||||
token = loginResult.token;
|
||||
console.log('login result', Date.now() - start, loginResult);
|
||||
}
|
||||
else {
|
||||
@@ -259,7 +277,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
|
||||
options?.previousLoginResult?.directAddress,
|
||||
options?.previousLoginResult?.cloudAddress,
|
||||
]) {
|
||||
if (u)
|
||||
if (u && options?.previousLoginResult?.token && (isNotChromeOrIsInstalledApp || options.direct))
|
||||
urlsToCheck.add(u);
|
||||
}
|
||||
|
||||
@@ -289,11 +307,15 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
|
||||
let loginCheck: Awaited<ReturnType<typeof checkScryptedClientLogin>>;
|
||||
try {
|
||||
loginCheck = await Promise.any(loginCheckPromises);
|
||||
tryAlternateAddresses ||= loginCheck.baseUrl !== baseUrl;
|
||||
}
|
||||
catch (e) {
|
||||
loginCheck = await baseUrlCheck;
|
||||
}
|
||||
|
||||
if (tryAlternateAddresses)
|
||||
console.log('Found direct login. Allowing alternate addresses.')
|
||||
|
||||
if (loginCheck.error || loginCheck.redirect)
|
||||
throw new ScryptedClientLoginError(loginCheck);
|
||||
localAddresses = loginCheck.addresses;
|
||||
@@ -303,6 +325,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
|
||||
username = loginCheck.username;
|
||||
authorization = loginCheck.authorization;
|
||||
queryToken = loginCheck.queryToken;
|
||||
token = loginCheck.token;
|
||||
console.log('login checked', Date.now() - start, loginCheck);
|
||||
}
|
||||
|
||||
@@ -323,24 +346,21 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
|
||||
// watch for this flush.
|
||||
const flush = new Deferred<void>();
|
||||
|
||||
// Chrome will complain about websites making xhr requests to self signed https sites, even
|
||||
// if the cert has been accepted. Other browsers seem fine.
|
||||
// So the default is not to connect to IP addresses on Chrome, but do so on other browsers.
|
||||
const isChrome = globalThis.navigator?.userAgent.includes('Chrome');
|
||||
const isNotChromeOrIsInstalledApp = !isChrome || isInstalledApp();
|
||||
|
||||
const addresses: string[] = [];
|
||||
const localAddressDefault = isNotChromeOrIsInstalledApp;
|
||||
if (((scryptedCloud && options.local === undefined && localAddressDefault) || options.local) && localAddresses) {
|
||||
|
||||
tryAlternateAddresses ||= scryptedCloud;
|
||||
|
||||
if (((tryAlternateAddresses && options.local === undefined && localAddressDefault) || options.local) && localAddresses) {
|
||||
addresses.push(...localAddresses);
|
||||
}
|
||||
|
||||
const directAddressDefault = directAddress && (isNotChromeOrIsInstalledApp || !isIPAddress(directAddress));
|
||||
if (((scryptedCloud && options.direct === undefined && directAddressDefault) || options.direct) && directAddress) {
|
||||
if (((tryAlternateAddresses && options.direct === undefined && directAddressDefault) || options.direct) && directAddress) {
|
||||
addresses.push(directAddress);
|
||||
}
|
||||
|
||||
if (((scryptedCloud && options.direct === undefined) || options.direct) && cloudAddress) {
|
||||
if (((tryAlternateAddresses && options.direct === undefined) || options.direct) && cloudAddress) {
|
||||
addresses.push(cloudAddress);
|
||||
}
|
||||
|
||||
@@ -686,6 +706,8 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
|
||||
browserSignalingSession,
|
||||
rpcPeer,
|
||||
loginResult: {
|
||||
username,
|
||||
token,
|
||||
directAddress,
|
||||
localAddresses,
|
||||
scryptedCloud,
|
||||
|
||||
@@ -364,6 +364,9 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
|
||||
|
||||
async testPortForward() {
|
||||
try {
|
||||
if (this.storageSettings.values.forwardingMode === 'Disabled')
|
||||
throw new Error('Port forwarding is disabled.');
|
||||
|
||||
const pluginPath = await endpointManager.getPath(undefined, {
|
||||
public: true,
|
||||
});
|
||||
|
||||
4
server/package-lock.json
generated
4
server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/server",
|
||||
"version": "0.51.0",
|
||||
"version": "0.54.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/server",
|
||||
"version": "0.51.0",
|
||||
"version": "0.54.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@mapbox/node-pre-gyp": "^1.0.11",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/server",
|
||||
"version": "0.51.0",
|
||||
"version": "0.55.0",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"@mapbox/node-pre-gyp": "^1.0.11",
|
||||
|
||||
@@ -474,21 +474,24 @@ async function start(mainFilename: string, options?: {
|
||||
res.send(200);
|
||||
});
|
||||
|
||||
const getAddresses = async () => {
|
||||
const getAlternateAddresses = async () => {
|
||||
const addresses = ((await scrypted.addressSettings.getLocalAddresses()) || getHostAddresses(true, true))
|
||||
.map(address => {
|
||||
if (ip.isV6Format(address) && !isV4Format(address))
|
||||
address = `[${address}]`;
|
||||
return `https://${address}:${SCRYPTED_SECURE_PORT}`
|
||||
});
|
||||
return addresses;
|
||||
return {
|
||||
externalAddresses: [...new Set(Object.values(scrypted.addressSettings.externalAddresses).flat())],
|
||||
addresses,
|
||||
};
|
||||
}
|
||||
|
||||
app.post('/login', async (req, res) => {
|
||||
const { username, password, change_password, maxAge: maxAgeRequested } = req.body;
|
||||
const timestamp = Date.now();
|
||||
const maxAge = parseInt(maxAgeRequested) || ONE_DAY_MILLISECONDS;
|
||||
const addresses = await getAddresses();
|
||||
const alternateAddresses = await getAlternateAddresses();
|
||||
|
||||
if (hasLogin) {
|
||||
const user = await db.tryGet(ScryptedUser, username);
|
||||
@@ -530,7 +533,7 @@ async function start(mainFilename: string, options?: {
|
||||
...createTokens(userToken),
|
||||
username,
|
||||
expiration: maxAge,
|
||||
addresses,
|
||||
...alternateAddresses,
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -561,7 +564,7 @@ async function start(mainFilename: string, options?: {
|
||||
username,
|
||||
token: user.token,
|
||||
expiration: maxAge,
|
||||
addresses,
|
||||
...alternateAddresses,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -582,7 +585,7 @@ async function start(mainFilename: string, options?: {
|
||||
await checkResetLogin();
|
||||
|
||||
const hostname = os.hostname()?.split('.')?.[0];
|
||||
const addresses = await getAddresses();
|
||||
const alternateAddresses = await getAlternateAddresses();
|
||||
|
||||
// env/header based admin login
|
||||
if (res.locals.username) {
|
||||
@@ -593,8 +596,9 @@ async function start(mainFilename: string, options?: {
|
||||
...createTokens(userToken),
|
||||
expiration: ONE_DAY_MILLISECONDS,
|
||||
username: res.locals.username,
|
||||
// TODO: do not return the token from a short term auth mechanism?
|
||||
token: user?.token,
|
||||
addresses,
|
||||
...alternateAddresses,
|
||||
hostname,
|
||||
});
|
||||
return;
|
||||
@@ -605,7 +609,7 @@ async function start(mainFilename: string, options?: {
|
||||
res.send({
|
||||
expiration: ONE_DAY_MILLISECONDS,
|
||||
username: 'anonymous',
|
||||
addresses,
|
||||
...alternateAddresses,
|
||||
hostname,
|
||||
})
|
||||
return;
|
||||
@@ -622,15 +626,19 @@ async function start(mainFilename: string, options?: {
|
||||
basicChecker(req, res);
|
||||
});
|
||||
|
||||
const user = await db.tryGet(ScryptedUser, username);
|
||||
const user = await db.tryGet(ScryptedUser, username) as ScryptedUser;
|
||||
if (!user.token) {
|
||||
user.token = crypto.randomBytes(16).toString('hex');
|
||||
await db.upsert(user);
|
||||
}
|
||||
|
||||
const userToken = new UserToken(user._id, user.aclId, Date.now());
|
||||
|
||||
res.send({
|
||||
...createTokens(userToken),
|
||||
username,
|
||||
token: user.token,
|
||||
addresses,
|
||||
...alternateAddresses,
|
||||
hostname,
|
||||
});
|
||||
return;
|
||||
@@ -646,7 +654,7 @@ async function start(mainFilename: string, options?: {
|
||||
...createTokens(userToken),
|
||||
expiration: (userToken.timestamp + userToken.duration) - Date.now(),
|
||||
username: userToken.username,
|
||||
addresses,
|
||||
...alternateAddresses,
|
||||
hostname,
|
||||
})
|
||||
}
|
||||
@@ -654,7 +662,7 @@ async function start(mainFilename: string, options?: {
|
||||
res.send({
|
||||
error: e?.message || 'Unknown Error.',
|
||||
hasLogin,
|
||||
addresses,
|
||||
...alternateAddresses,
|
||||
hostname,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,9 +3,21 @@ import { ScryptedRuntime } from "../runtime";
|
||||
import os from 'os';
|
||||
|
||||
export class AddressSettings {
|
||||
externalAddresses: {
|
||||
[id: string]: string[],
|
||||
} = {};
|
||||
|
||||
constructor(public scrypted: ScryptedRuntime) {
|
||||
}
|
||||
|
||||
async getExternalAddresses(id: string): Promise<string[]> {
|
||||
return this.externalAddresses[id] || [];
|
||||
}
|
||||
|
||||
async setExternalAddresses(id: string, addresses: string[]) {
|
||||
this.externalAddresses[id] = addresses;
|
||||
}
|
||||
|
||||
async setLocalAddresses(addresses: string[]) {
|
||||
const localAddresses = new Settings();
|
||||
localAddresses._id = 'localAddresses';
|
||||
|
||||
Reference in New Issue
Block a user