Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Change Log

## 22.1.3

* Added: `--resource` option to `oauth2 authorize`, `create-device-authorization`, and `create-token` for RFC 8707 resource indicators
* Added: Press Enter during device login to open the verification URL in your browser
* Updated: Bumped `@appwrite.io/console` dependency to `^15.1.0`

## 22.1.2

* Added: OAuth2 login now requests the `account.admin` scope
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Once the installation is complete, you can verify the install using

```sh
$ appwrite -v
22.1.2
22.1.3
```

### Install using prebuilt binaries
Expand Down Expand Up @@ -83,7 +83,7 @@ $ scoop install https://raw.githubusercontent.com/appwrite/sdk-for-cli/master/sc
Once the installation completes, you can verify your install using
```
$ appwrite -v
22.1.2
22.1.3
```

## Getting Started
Expand Down
4 changes: 2 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
# You can use "View source" of this page to see the full script.

# REPO
$GITHUB_x64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/22.1.2/appwrite-cli-win-x64.exe"
$GITHUB_arm64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/22.1.2/appwrite-cli-win-arm64.exe"
$GITHUB_x64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/22.1.3/appwrite-cli-win-x64.exe"
$GITHUB_arm64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/22.1.3/appwrite-cli-win-arm64.exe"

$APPWRITE_BINARY_NAME = "appwrite.exe"

Expand Down
2 changes: 1 addition & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ verifyMacOSCodeSignature() {
downloadBinary() {
echo "[2/5] Downloading executable for $OS ($ARCH) ..."

GITHUB_LATEST_VERSION="22.1.2"
GITHUB_LATEST_VERSION="22.1.3"
GITHUB_FILE="appwrite-cli-${OS}-${ARCH}"
GITHUB_URL="https://github.com/$GITHUB_REPOSITORY_NAME/releases/download/$GITHUB_LATEST_VERSION/$GITHUB_FILE"

Expand Down
74 changes: 72 additions & 2 deletions lib/auth/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import {
OAUTH2_SCOPES,
} from "../constants.js";
import { success, hint, warn, log } from "../parser.js";
import { isCloudLoginEndpoint, isRegionalCloudEndpoint } from "../utils.js";
import {
isCloudLoginEndpoint,
isRegionalCloudEndpoint,
openBrowser,
} from "../utils.js";
import { isFlagEnabled } from "../flags.js";
import ID from "../id.js";
import {
Expand Down Expand Up @@ -67,6 +71,58 @@ const startWaitingForApprovalSpinner = (): (() => void) => {
};
};

const listenForBrowserOpen = (
url: string,
onCancel: () => void,
): (() => void) => {
const stdin = process.stdin as NodeJS.ReadStream & {
isRaw?: boolean;
setRawMode?: (mode: boolean) => void;
};
if (!stdin.isTTY) {
return () => {};
}

// Raw mode so keypresses aren't echoed onto the spinner line; the trade-off
// is that the terminal no longer turns Ctrl+C/Ctrl+D into a signal for us.
const canSetRawMode = typeof stdin.setRawMode === "function";
const shouldRestoreRawMode = canSetRawMode && stdin.isRaw !== true;
if (shouldRestoreRawMode) {
stdin.setRawMode?.(true);
}
const shouldPause = stdin.isPaused();
stdin.resume();

const cleanup = (): void => {
stdin.off("data", onData);
if (shouldRestoreRawMode) {
stdin.setRawMode?.(false);
}
if (shouldPause) {
stdin.pause();
}
};

// Open the browser at most once; keep listening afterwards so Ctrl+C still
// cancels cleanly even after the user has opened the URL.
let opened = false;
const onData = (data: Buffer): void => {
if (data.includes(0x03) || data.includes(0x04)) {
cleanup();
onCancel();
return;
}
if (!opened && (data.includes(0x0d) || data.includes(0x0a))) {
opened = true;
openBrowser(url);
}
};

stdin.on("data", onData);

return cleanup;
};

export const getCurrentAccount = async (): Promise<Models.User | null> => {
if (globalConfig.getEndpoint() === "" || !hasAuthSession()) {
return null;
Expand Down Expand Up @@ -299,11 +355,24 @@ const loginWithOAuthDevice = async ({
process.stdout.write(
"\nTo sign in, confirm the code below in your browser:\n\n" +
` Code: ${deviceAuth.user_code}\n` +
` URL: ${verificationUri}\n\n`,
` URL: ${verificationUri}\n\n` +
(process.stdin.isTTY
? "Press Enter to open this URL in your browser.\n\n"
: ""),
);

let token: Models.Oauth2Token | null = null;
const stopWaitingForApprovalSpinner = startWaitingForApprovalSpinner();
const stopListeningForBrowserOpen = listenForBrowserOpen(
verificationUri,
() => {
stopWaitingForApprovalSpinner();
globalConfig.removeSession(id);
globalConfig.setCurrentSession(oldCurrent);
log("Login cancelled.");
process.exit(130);
},
);

try {
token = await pollForDeviceToken(oauth2, deviceAuth, clientId);
Expand All @@ -313,6 +382,7 @@ const loginWithOAuthDevice = async ({
throw err;
} finally {
stopWaitingForApprovalSpinner();
stopListeningForBrowserOpen();
}

if (!token || !token.access_token) {
Expand Down
15 changes: 9 additions & 6 deletions lib/commands/services/oauth-2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@ const oauth2AuthorizeCommand = oauth2
.option(`--prompt <prompt>`, `OIDC prompt parameter for customization of consent screen. Space-separated list of: none, login, consent, select_account.`)
.option(`--max-_age <max-_age>`, `OIDC max_age paraleter for customization of consent screen. Maximum allowable elapsed time in seconds since the user last authenticated. If exceeded, re-authentication is required.`, parseInteger)
.option(`--authorization-_details <authorization-_details>`, `Rich authorization request. JSON array of objects, each with a \`type\` and project-defined fields`)
.option(`--resource <resource>`, `RFC 8707 resource indicator URI or URI list. Each value must be an absolute URI without a fragment.`)
.action(
actionRunner(
async ({ client_id, redirect_uri, response_type, scope, state, nonce, code_challenge, code_challenge_method, prompt, max_age, authorization_details }) =>
parse(await (await getOauth2Client()).authorize(client_id, redirect_uri, response_type, scope, state, nonce, code_challenge, code_challenge_method, prompt, max_age, authorization_details)),
async ({ client_id, redirect_uri, response_type, scope, state, nonce, code_challenge, code_challenge_method, prompt, max_age, authorization_details, resource }) =>
parse(await (await getOauth2Client()).authorize(client_id, redirect_uri, response_type, scope, state, nonce, code_challenge, code_challenge_method, prompt, max_age, authorization_details, resource)),
Comment thread
ChiragAgg5k marked this conversation as resolved.
),
);

Expand All @@ -67,10 +68,11 @@ const oauth2CreateDeviceAuthorizationCommand = oauth2
.option(`--client-_id <client-_id>`, `OAuth2 client ID.`)
.option(`--scope <scope>`, `Space-separated OAuth2 scopes. Can include project scopes, and built-in scopes: \`openid\`, \`email\`, \`profile\`.`)
.option(`--authorization-_details <authorization-_details>`, `Rich authorization request. JSON array of objects, each with a \`type\` and project-defined fields`)
.option(`--resource <resource>`, `RFC 8707 resource indicator URI or URI list. Each value must be an absolute URI without a fragment.`)
.action(
actionRunner(
async ({ client_id, scope, authorization_details }) =>
parse(await (await getOauth2Client()).createDeviceAuthorization(client_id, scope, authorization_details)),
async ({ client_id, scope, authorization_details, resource }) =>
parse(await (await getOauth2Client()).createDeviceAuthorization(client_id, scope, authorization_details, resource)),
),
);

Expand Down Expand Up @@ -154,10 +156,11 @@ const oauth2CreateTokenCommand = oauth2
.option(`--client-_secret <client-_secret>`, `OAuth2 client secret. Required for confidential apps.`)
.option(`--code-_verifier <code-_verifier>`, `PKCE code verifier. Required for public apps.`)
.option(`--redirect-_uri <redirect-_uri>`, `Redirect URI. Required for \`authorization_code\` grant type.`)
.option(`--resource <resource>`, `RFC 8707 resource indicator URI or URI list. Each value must be an absolute URI without a fragment.`)
.action(
actionRunner(
async ({ grant_type, code, refresh_token, device_code, client_id, client_secret, code_verifier, redirect_uri }) =>
parse(await (await getOauth2Client()).createToken(grant_type, code, refresh_token, device_code, client_id, client_secret, code_verifier, redirect_uri)),
async ({ grant_type, code, refresh_token, device_code, client_id, client_secret, code_verifier, redirect_uri, resource }) =>
parse(await (await getOauth2Client()).createToken(grant_type, code, refresh_token, device_code, client_id, client_secret, code_verifier, redirect_uri, resource)),
),
);

Expand Down
2 changes: 1 addition & 1 deletion lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SDK
export const SDK_TITLE = 'Appwrite';
export const SDK_TITLE_LOWER = 'appwrite';
export const SDK_VERSION = '22.1.2';
export const SDK_VERSION = '22.1.3';
export const SDK_NAME = 'Command Line';
export const SDK_PLATFORM = 'console';
export const SDK_LANGUAGE = 'cli';
Expand Down
36 changes: 36 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,42 @@ export function systemHasCommand(command: string): boolean {
return true;
}

// Best-effort, fire-and-forget: spawn reports a missing/failed opener via an
// async `error` event (not a throw), so there's no reliable success value to
// return — failures just mean the user opens the printed URL manually.
export function openBrowser(url: string): void {
let command: string;
let args: string[];

switch (process.platform) {
case "win32":
command = "cmd";
// "" is start's window-title arg; quoting the URL stops cmd from
// splitting it on `&` (and running the remainder as a command).
args = ["/c", "start", "", `"${url}"`];
break;
case "darwin":
command = "open";
args = [url];
break;
default:
command = "xdg-open";
args = [url];
break;
}

try {
const child = childProcess.spawn(command, args, {
stdio: "ignore",
detached: true,
});
child.on("error", () => {});
child.unref();
} catch {
// Ignore synchronous spawn failures; opening the browser is best-effort.
}
}

type DeployLocalConfig = {
keys: () => string[];
};
Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"type": "module",
"homepage": "https://appwrite.io/support",
"description": "Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API",
"version": "22.1.2",
"version": "22.1.3",
"license": "BSD-3-Clause",
"main": "dist/index.cjs",
"module": "dist/index.js",
Expand Down Expand Up @@ -51,7 +51,7 @@
"windows-arm64": "bun build cli.ts --compile --minify --target=bun-windows-arm64 --outfile build/appwrite-cli-win-arm64.exe"
},
"dependencies": {
"@appwrite.io/console": "^15.0.0",
"@appwrite.io/console": "^15.1.0",
"chalk": "4.1.2",
"chokidar": "^3.6.0",
"cli-progress": "^3.12.0",
Expand Down
6 changes: 3 additions & 3 deletions scoop/appwrite.config.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"$schema": "https://raw.githubusercontent.com/ScoopInstaller/Scoop/master/schema.json",
"version": "22.1.2",
"version": "22.1.3",
"description": "The Appwrite CLI is a command-line application that allows you to interact with Appwrite and perform server-side tasks using your terminal.",
"homepage": "https://github.com/appwrite/sdk-for-cli",
"license": "BSD-3-Clause",
"architecture": {
"64bit": {
"url": "https://github.com/appwrite/sdk-for-cli/releases/download/22.1.2/appwrite-cli-win-x64.exe",
"url": "https://github.com/appwrite/sdk-for-cli/releases/download/22.1.3/appwrite-cli-win-x64.exe",
"bin": [
[
"appwrite-cli-win-x64.exe",
Expand All @@ -15,7 +15,7 @@
]
},
"arm64": {
"url": "https://github.com/appwrite/sdk-for-cli/releases/download/22.1.2/appwrite-cli-win-arm64.exe",
"url": "https://github.com/appwrite/sdk-for-cli/releases/download/22.1.3/appwrite-cli-win-arm64.exe",
"bin": [
[
"appwrite-cli-win-arm64.exe",
Expand Down