Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 75e9e24

Browse files
Merge pull request #3277 from code-asher/logout
2 parents b9ff73a + 8b2c78c commit 75e9e24

File tree

16 files changed

+128
-126
lines changed

16 files changed

+128
-126
lines changed

‎lib/vscode/.eslintignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@
1919
# These are code-server code symlinks.
2020
src/vs/base/node/proxy_agent.ts
2121
src/vs/ipc.d.ts
22+
src/vs/server/common/util.ts

‎lib/vscode/src/vs/server/browser/client.ts‎

Lines changed: 40 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import * as path from 'vs/base/common/path';
2-
import { URI } from 'vs/base/common/uri';
32
import { Options } from 'vs/ipc';
43
import { localize } from 'vs/nls';
4+
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
5+
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
56
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
7+
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
68
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
79
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
810
import { ILogService } from 'vs/platform/log/common/log';
@@ -11,10 +13,18 @@ import { Registry } from 'vs/platform/registry/common/platform';
1113
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
1214
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
1315
import { TelemetryChannelClient } from 'vs/server/common/telemetry';
16+
import { getOptions } from 'vs/server/common/util';
1417
import 'vs/workbench/contrib/localizations/browser/localizations.contribution';
1518
import 'vs/workbench/services/localizations/browser/localizationsService';
1619
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
1720

21+
/**
22+
* All client-side customization to VS Code should live in this file when
23+
* possible.
24+
*/
25+
26+
const options = getOptions<Options>();
27+
1828
class TelemetryService extends TelemetryChannelClient {
1929
public constructor(
2030
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
@@ -23,26 +33,6 @@ class TelemetryService extends TelemetryChannelClient {
2333
}
2434
}
2535

26-
/**
27-
* Remove extra slashes in a URL.
28-
*/
29-
export const normalize = (url: string, keepTrailing = false): string => {
30-
return url.replace(/\/\/+/g, '/').replace(/\/+$/, keepTrailing ? '/' : '');
31-
};
32-
33-
/**
34-
* Get options embedded in the HTML.
35-
*/
36-
export const getOptions = <T extends Options>(): T => {
37-
try {
38-
return JSON.parse(document.getElementById('coder-options')!.getAttribute('data-settings')!);
39-
} catch (error) {
40-
return {} as T;
41-
}
42-
};
43-
44-
const options = getOptions();
45-
4636
const TELEMETRY_SECTION_ID = 'telemetry';
4737
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
4838
'id': TELEMETRY_SECTION_ID,
@@ -173,38 +163,36 @@ export const initialize = async (services: ServiceCollection): Promise<void> =>
173163
if (theme) {
174164
localStorage.setItem('colorThemeData', theme);
175165
}
176-
};
177166

178-
export interface Query {
179-
[key: string]: string | undefined;
180-
}
181-
182-
/**
183-
* Split a string up to the delimiter. If the delimiter doesn't exist the first
184-
* item will have all the text and the second item will be an empty string.
185-
*/
186-
export const split = (str: string, delimiter: string): [string, string] => {
187-
const index = str.indexOf(delimiter);
188-
return index !== -1 ? [str.substring(0, index).trim(), str.substring(index + 1)] : [str, ''];
189-
};
167+
// Use to show or hide logout commands and menu options.
168+
const contextKeyService = (services.get(IContextKeyService) as IContextKeyService);
169+
contextKeyService.createKey('code-server.authed', options.authed);
170+
171+
// Add a logout command.
172+
const logoutEndpoint = path.join(options.base, '/logout') + `?base=${options.base}`;
173+
const LOGOUT_COMMAND_ID = 'code-server.logout';
174+
CommandsRegistry.registerCommand(
175+
LOGOUT_COMMAND_ID,
176+
() => {
177+
window.location.href = logoutEndpoint;
178+
},
179+
);
180+
181+
// Add logout to command palette.
182+
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
183+
command: {
184+
id: LOGOUT_COMMAND_ID,
185+
title: localize('logout', "Log out")
186+
},
187+
when: ContextKeyExpr.has('code-server.authed')
188+
});
190189

191-
/**
192-
* Return the URL modified with the specified query variables. It's pretty
193-
* stupid so it probably doesn't cover any edge cases. Undefined values will
194-
* unset existing values. Doesn't allow duplicates.
195-
*/
196-
export const withQuery = (url: string, replace: Query): string => {
197-
const uri = URI.parse(url);
198-
const query = { ...replace };
199-
uri.query.split('&').forEach((kv) => {
200-
const [key, value] = split(kv, '=');
201-
if (!(key in query)) {
202-
query[key] = value;
203-
}
190+
// Add logout to the (web-only) home menu.
191+
MenuRegistry.appendMenuItem(MenuId.MenubarHomeMenu, {
192+
command: {
193+
id: LOGOUT_COMMAND_ID,
194+
title: localize('logout', "Log out")
195+
},
196+
when: ContextKeyExpr.has('code-server.authed')
204197
});
205-
return uri.with({
206-
query: Object.keys(query)
207-
.filter((k) => typeof query[k] !== 'undefined')
208-
.map((k) => `${k}=${query[k]}`).join('&'),
209-
}).toString(true);
210198
};

‎lib/vscode/src/vs/server/common/cookie.ts‎

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../../../../src/common/util.ts

‎lib/vscode/src/vs/workbench/browser/parts/titlebar/menubarControl.ts‎

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { registerThemingParticipant, IThemeService } from 'vs/platform/theme/com
99
import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable, getMenuBarVisibility } from 'vs/platform/windows/common/windows';
1010
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
1111
import { IAction, Action, SubmenuAction, Separator } from 'vs/base/common/actions';
12-
import { addDisposableListener, Dimension, EventType,getCookieValue } from 'vs/base/browser/dom';
12+
import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom';
1313
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
1414
import { isMacintosh, isWeb, isIOS, isNative } from 'vs/base/common/platform';
1515
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
@@ -38,8 +38,6 @@ import { KeyCode } from 'vs/base/common/keyCodes';
3838
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
3939
import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
4040
import { ICommandService } from 'vs/platform/commands/common/commands';
41-
import { ILogService } from 'vs/platform/log/common/log';
42-
import { Cookie } from 'vs/server/common/cookie';
4341

4442
export type IOpenRecentAction = IAction & { uri: URI, remoteAuthority?: string };
4543

@@ -318,8 +316,7 @@ export class CustomMenubarControl extends MenubarControl {
318316
@IThemeService private readonly themeService: IThemeService,
319317
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
320318
@IHostService protected readonly hostService: IHostService,
321-
@ICommandService commandService: ICommandService,
322-
@ILogService private readonly logService: ILogService
319+
@ICommandService commandService: ICommandService
323320
) {
324321
super(menuService, workspacesService, contextKeyService, keybindingService, configurationService, labelService, updateService, storageService, notificationService, preferencesService, environmentService, accessibilityService, hostService, commandService);
325322

@@ -721,28 +718,6 @@ export class CustomMenubarControl extends MenubarControl {
721718
webNavigationActions.pop();
722719
}
723720

724-
webNavigationActions.push(new Action('logout', localize('logout', "Log out"), undefined, true,
725-
async (event?: MouseEvent) => {
726-
const COOKIE_KEY = Cookie.Key;
727-
const loginCookie = getCookieValue(COOKIE_KEY);
728-
729-
this.logService.info('Logging out of code-server');
730-
731-
if(loginCookie) {
732-
this.logService.info(`Removing cookie under ${COOKIE_KEY}`);
733-
734-
if (document && document.cookie) {
735-
// We delete the cookie by setting the expiration to a date/time in the past
736-
document.cookie = COOKIE_KEY +'=; Path=/; Expires=1970年1月01日 00:00:01 GMT;';
737-
window.location.href = '/login';
738-
} else {
739-
this.logService.warn('Could not delete cookie because document and/or document.cookie is undefined');
740-
}
741-
} else {
742-
this.logService.warn('Could not log out because we could not find cookie');
743-
}
744-
}));
745-
746721
return webNavigationActions;
747722
}
748723

‎src/browser/register.ts‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { logger } from "@coder/logger"
12
import { getOptions, normalize, logError } from "../common/util"
23

34
import "./pages/error.css"
@@ -6,19 +7,21 @@ import "./pages/login.css"
67

78
export async function registerServiceWorker(): Promise<void> {
89
const options = getOptions()
10+
logger.level = options.logLevel
11+
912
const path = normalize(`${options.csStaticBase}/dist/serviceWorker.js`)
1013
try {
1114
await navigator.serviceWorker.register(path, {
1215
scope: options.base + "/",
1316
})
14-
console.log("[Service Worker] registered")
17+
logger.info(`[Service Worker] registered`)
1518
} catch (error) {
16-
logError(`[Service Worker] registration`, error)
19+
logError(logger,`[Service Worker] registration`, error)
1720
}
1821
}
1922

2023
if (typeof navigator !== "undefined" && "serviceWorker" in navigator) {
2124
registerServiceWorker()
2225
} else {
23-
console.error(`[Service Worker] navigator is undefined`)
26+
logger.error(`[Service Worker] navigator is undefined`)
2427
}

‎src/common/util.ts‎

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
import { logger, field } from "@coder/logger"
1+
/*
2+
* This file exists in two locations:
3+
* - src/common/util.ts
4+
* - lib/vscode/src/vs/server/common/util.ts
5+
* The second is a symlink to the first.
6+
*/
27

8+
/**
9+
* Base options included on every page.
10+
*/
311
export interface Options {
412
base: string
513
csStaticBase: string
@@ -69,6 +77,9 @@ export const getOptions = <T extends Options>(): T => {
6977
options = {} as T
7078
}
7179

80+
// You can also pass options in stringified form to the options query
81+
// variable. Options provided here will override the ones in the options
82+
// element.
7283
const params = new URLSearchParams(location.search)
7384
const queryOpts = params.get("options")
7485
if (queryOpts) {
@@ -78,13 +89,9 @@ export const getOptions = <T extends Options>(): T => {
7889
}
7990
}
8091

81-
logger.level = options.logLevel
82-
8392
options.base = resolveBase(options.base)
8493
options.csStaticBase = resolveBase(options.csStaticBase)
8594

86-
logger.debug("got options", field("options", options))
87-
8895
return options
8996
}
9097

@@ -113,7 +120,8 @@ export const getFirstString = (value: string | string[] | object | undefined): s
113120
return typeof value === "string" ? value : undefined
114121
}
115122

116-
export function logError(prefix: string, err: any): void {
123+
// TODO: Might make sense to add Error handling to the logger itself.
124+
export function logError(logger: { error: (msg: string) => void }, prefix: string, err: Error | string): void {
117125
if (err instanceof Error) {
118126
logger.error(`${prefix}: ${err.message} ${err.stack}`)
119127
} else {

‎src/node/app.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export const createApp = async (args: DefaultedArgs): Promise<[Express, Express,
3737
reject(err)
3838
} else {
3939
// Promise resolved earlier so this is an unrelated error.
40-
util.logError("http server error", err)
40+
util.logError(logger,"http server error", err)
4141
}
4242
})
4343

‎src/node/routes/index.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import * as apps from "./apps"
2020
import * as domainProxy from "./domainProxy"
2121
import * as health from "./health"
2222
import * as login from "./login"
23+
import * as logout from "./logout"
2324
import * as pathProxy from "./pathProxy"
2425
// static is a reserved keyword.
2526
import * as _static from "./static"
@@ -136,10 +137,10 @@ export const register = async (
136137

137138
if (args.auth === AuthType.Password) {
138139
app.use("/login", login.router)
140+
app.use("/logout", logout.router)
139141
} else {
140-
app.all("/login", (req, res) => {
141-
redirect(req, res, "/", {})
142-
})
142+
app.all("/login", (req, res) => redirect(req, res, "/", {}))
143+
app.all("/logout", (req, res) => redirect(req, res, "/", {}))
143144
}
144145

145146
app.use("/static", _static.router)

‎src/node/routes/logout.ts‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Router } from "express"
2+
import { getCookieDomain, redirect } from "../http"
3+
import { Cookie } from "./login"
4+
5+
export const router = Router()
6+
7+
router.get("/", async (req, res) => {
8+
// Must use the *identical* properties used to set the cookie.
9+
res.clearCookie(Cookie.Key, {
10+
domain: getCookieDomain(req.headers.host || "", req.args["proxy-domain"]),
11+
path: req.body.base || "/",
12+
sameSite: "lax",
13+
})
14+
15+
const to = (typeof req.query.to === "string" && req.query.to) || "/"
16+
return redirect(req, res, to, { to: undefined, base: undefined })
17+
})

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /