Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Persist servers by access key #988

Merged
merged 18 commits into from
Mar 23, 2021
Merged

Persist servers by access key #988

merged 18 commits into from
Mar 23, 2021

Conversation

alalamav
Copy link
Contributor

@alalamav alalamav commented Mar 8, 2021

The motivation behind this is to lay the groundwork for a single persistence format in the context of dynamic access keys (online config):

  • Changes the server persistence format to {accessKey, name}, from ShadowsocksConfig.
  • Implements unit tests for the data storage migration.
  • Updates the Tunnel interface not to own a ShadowsocksConfig.
  • Removes ServerConfig, PersitentServer and PersitentServerFactory.
  • Moves the ShadowsocksConfig definition out of model.

@alalamav alalamav requested a review from fortuna March 8, 2021 19:50
@alalamav alalamav self-assigned this Mar 8, 2021
src/www/app/outline_server.ts Outdated Show resolved Hide resolved
src/www/infrastructure/memory_storage.ts Show resolved Hide resolved
src/www/app/config.ts Outdated Show resolved Hide resolved
return this.serverById.get(serverId);
}

add(accessKey: string, serverName: string) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make add not take the serverName. I'd like this to be aligned with the product.
It's ok to have unnamed servers. The application/ui code should have the logic: if server has a name, use it, otherwise use the default name. This is the same approach we use for access keys in the manager.

In order to support Outline/Shadowsocks Server, you'll need to expose a getter to differentiate them.

Note that the access key may have a name, so we can leverage that for the fake servers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 76bde1c. I also changed the persistence format to encode the name. In order to support default (outlne/non-outline) server name, I added an extra field to ShadowsocksConfig.

getPersistentServerFactory: () => {
return (serverId: string, config: ServerConfig, eventQueue: EventQueue) => {
getServerFactory: () => {
return (serverId: string, serverName: string, config: ShadowsocksConfig,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert serverName. See my other comment for explanation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 76bde1c.

@alalamav alalamav requested a review from fortuna March 15, 2021 22:19
Copy link
Collaborator

@fortuna fortuna left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I think we can tweak the storage format, but all the remaining changes are localized there.

@@ -543,8 +523,18 @@ export class App {
this.rootEl.changePage(this.rootEl.DEFAULT_PAGE);
}

// Returns the server's name, if present, or a default server name.
private getServerName(server: Server): string {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd name this getServerDisplayName(server), so it's clearer it's the name for display.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 7a6d05e.

}

set name(newName: string) {
this.config.name = newName;
}

get host() {
return this.config.host;
get accessKey() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this used? Delete if not used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OutlineServer needs to expose the access key so the repo can find servers by access key to prevent adding duplicate servers.

public readonly createServer: OutlineServerFactory, private eventQueue: events.EventQueue,
private storage: Storage) {
try {
migrateServerStorageToV1(this.storage);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need to explicitly migrate if there were no changes.
Instead, just call loadServers(). loadServers() will have to try v1 and fallback to v0 forever anyway.

Then, on storeServers(), you write the new format when change happens.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in bc68e17.

src/www/app/outline_server.ts Show resolved Hide resolved
return cipher !== undefined && OutlineServer.SUPPORTED_CIPHERS.includes(cipher);
}
}

// DEPRECATED: V0 server persistence format.
export interface ConfigById {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps name them ServersStorageV0 and ServersStorageV1?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in bc68e17.

}

// V1 server persistence format.
export interface AccessKeyById {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A dictionary is not a very convenient format, because the type is weird. It's not a fixed schema, since you have variable fields. It's better to use a list.

Also, it seems the access key is not all we need.

I think we need something like:

type AccessKey = string;

interface ServerJson {
  id: string
  name?:  string
  accessKey: AccessKey  // device key for connection. Whatever you get from the permanent redirects.
  sharedAccessKey?: AccessKey  // multi-user key for sharing
}

accessKey could be name connectionAccessKey.
sharedAccessKey could have other names, such as originalAccessKey.

The is_outline bit could be derived from the sharedAccessKey, or accessKey if absent.

The localStorage format under servers_v1 would be ServerJson[].

Copy link
Contributor Author

@alalamav alalamav Mar 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice suggestion. Done in bc68e17 (without multi-user keys). I also updated the storage migration unit tests.

@@ -19,4 +19,5 @@ export interface ShadowsocksConfig {
password?: string;
method?: string;
name?: string;
extra?: {[key: string]: string};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove this. See my comments on the storage format.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in bc68e17. We now preserve the original access key and inject it into OutlineServer.

@alalamav alalamav requested a review from fortuna March 18, 2021 03:12
for (const serverId in configByAccessKey) {
if (configByAccessKey.hasOwnProperty(serverId)) {
const accessKey = configByAccessKey[serverId];
for (const serverId in configById) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to change, but I think for (const serverId of Object.keys(configById)) is the right way to do this. It removes the need for the hasOwnProperty below, I believe.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 3e18673.

}
}

private loadServer(serverId: string, accessKey: string, config: ShadowsocksConfig) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change loadServer to take one parameter: OutlineServerJson.
It will simplify the loadServer methods.
Also, if you can't load the server from the OutlineServerJson, something is wrong. That will make it clearer what is needed to load a server.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 3e18673.

src/www/app/outline_server.ts Show resolved Hide resolved
@@ -31,8 +31,9 @@ export class OutlineServer implements Server {
errorMessageId?: string;

constructor(
public readonly id: string, private readonly config: ShadowsocksConfig,
private tunnel: Tunnel, private eventQueue: events.EventQueue) {
public readonly id: string, public readonly accessKey: string,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's confusing to have both the accessKey and the config.
Can't we have the same paramters as OutlineServerJson?
Pass id, name, and accessKey (besides tunnel and eventQueue).
We need to change the server factory accordingly too.

I think we can ditch ShadowsocksConfig for the most part, and have something specific for the proxying.

We have a design problem with the factory. We should eventually get rid of that, this code should control that. Instead we could be injecting a tunnel factory instead, but that a bit of a bigger change we can do later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 3e18673. I updated the server factory and OutlineServer constructor per your suggestion. I think it's clearer now, at the expense of parsing the access key twice, since the server repository needs to flag unsupported ciphers for backwards compatibility.

I kept the serve name getter/setter in order to update config.name, which is used by native code.

We have a design problem with the factory.

I have upcoming changes that will refactor the server factory into an interface that will allow native platforms to create only the tunnel.

@alalamav alalamav requested a review from fortuna March 22, 2021 19:49
@alalamav alalamav merged commit 74dcb85 into master Mar 23, 2021
@alalamav alalamav deleted the alalama-server-persistence branch March 23, 2021 22:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants