Compare commits

...

11 Commits

Author SHA1 Message Date
Koushik Dutta
0a4336879c client: allow direct login on chrome if flag is explicitly true 2023-09-19 19:03:16 -07:00
Koushik Dutta
e5cef3f217 client: fixup alt address usage 2023-09-19 18:57:15 -07:00
Koushik Dutta
d34396afbc postbeta 2023-09-19 18:56:45 -07:00
Koushik Dutta
2622fc9256 postbeta 2023-09-19 16:46:09 -07:00
Koushik Dutta
410b1a4813 client: check token presence before using direct address 2023-09-19 16:25:08 -07:00
Koushik Dutta
403c742be3 server: token comment 2023-09-19 16:21:10 -07:00
Koushik Dutta
50a471b78f client: use long term token for direct connection 2023-09-19 15:26:23 -07:00
Koushik Dutta
9b7ead26e0 postbeta 2023-09-19 15:23:07 -07:00
Koushik Dutta
3127bc38cb server: include token for basic auth login result 2023-09-19 15:22:48 -07:00
Koushik Dutta
fb8b1a893d cloud: fix misleading port forward test error 2023-09-19 13:46:26 -07:00
Koushik Dutta
779d8eaa42 postrelease 2023-09-19 13:39:29 -07:00
6 changed files with 73 additions and 28 deletions

View File

@@ -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,

View File

@@ -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,
});

View File

@@ -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",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/server",
"version": "0.51.0",
"version": "0.55.0",
"description": "",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11",

View File

@@ -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,
})
}

View File

@@ -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';