Skip to content

Commit 3255cfc

Browse files
committed
feat(auth): add token command and remove /users/me/ dependency
- Add `sentry auth token` command that prints the unmasked auth token - Use `/users/me/regions/` for token validation (works with all token types) - Optionally fetch user info via `/users/me/` after validation (works with API tokens) - Suppress upgrade notification for `auth token` (scripting use case) Note: User identity for OAuth login comes from the token response. For manual API tokens, we try /users/me/ which works with API tokens but may not work with OAuth App tokens - failures are handled gracefully.
1 parent 4164a09 commit 3255cfc

6 files changed

Lines changed: 66 additions & 15 deletions

File tree

plugins/sentry-cli/skills/sentry-cli/SKILL.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ View authentication status
9090
sentry auth status
9191
```
9292

93+
#### `sentry auth token`
94+
95+
Print the stored authentication token
96+
9397
### Org
9498

9599
Work with Sentry organizations

src/commands/auth/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,22 @@ import { loginCommand } from "./login.js";
33
import { logoutCommand } from "./logout.js";
44
import { refreshCommand } from "./refresh.js";
55
import { statusCommand } from "./status.js";
6+
import { tokenCommand } from "./token.js";
67

78
export const authRoute = buildRouteMap({
89
routes: {
910
login: loginCommand,
1011
logout: logoutCommand,
1112
refresh: refreshCommand,
1213
status: statusCommand,
14+
token: tokenCommand,
1315
},
1416
docs: {
1517
brief: "Authenticate with Sentry",
1618
fullDescription:
1719
"Manage authentication with Sentry. Use 'sentry auth login' to authenticate, " +
1820
"'sentry auth logout' to remove credentials, 'sentry auth refresh' to manually refresh your token, " +
19-
"and 'sentry auth status' to check your authentication status.",
21+
"'sentry auth status' to check your authentication status, " +
22+
"and 'sentry auth token' to print your token for use in scripts.",
2023
},
2124
});

src/commands/auth/login.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
1-
// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import
2-
import * as Sentry from "@sentry/bun";
31
import { buildCommand, numberParser } from "@stricli/core";
42
import type { SentryContext } from "../../context.js";
5-
import { getCurrentUser } from "../../lib/api-client.js";
3+
import { getCurrentUser, getUserRegions } from "../../lib/api-client.js";
64
import { clearAuth, isAuthenticated, setAuthToken } from "../../lib/db/auth.js";
75
import { getDbPath } from "../../lib/db/index.js";
86
import { setUserInfo } from "../../lib/db/user.js";
97
import { AuthError } from "../../lib/errors.js";
108
import { muted, success } from "../../lib/formatters/colors.js";
119
import { formatUserIdentity } from "../../lib/formatters/human.js";
1210
import { runInteractiveLogin } from "../../lib/interactive-login.js";
13-
import type { SentryUser } from "../../types/index.js";
1411

1512
type LoginFlags = {
1613
readonly token?: string;
@@ -55,13 +52,12 @@ export const loginCommand = buildCommand({
5552

5653
// Token-based authentication
5754
if (flags.token) {
58-
// Save token first, then validate by fetching user info
55+
// Save token first, then validate by fetching user regions
5956
await setAuthToken(flags.token);
6057

61-
// Validate token by fetching user info
62-
let user: SentryUser;
58+
// Validate token by fetching user regions
6359
try {
64-
user = await getCurrentUser();
60+
await getUserRegions();
6561
} catch {
6662
// Token is invalid - clear it and throw
6763
await clearAuth();
@@ -71,21 +67,24 @@ export const loginCommand = buildCommand({
7167
);
7268
}
7369

74-
// Store user info for telemetry (non-critical, don't block auth)
70+
// Try to get user info (works with API tokens, may not work with OAuth App tokens)
71+
let user: Awaited<ReturnType<typeof getCurrentUser>> | undefined;
7572
try {
73+
user = await getCurrentUser();
7674
setUserInfo({
7775
userId: user.id,
7876
email: user.email,
7977
username: user.username,
8078
name: user.name,
8179
});
82-
} catch (error) {
83-
// Report to Sentry but don't block auth - user info is not critical
84-
Sentry.captureException(error);
80+
} catch {
81+
// Ignore - user info is optional, token may not have permission
8582
}
8683

8784
stdout.write(`${success("✓")} Authenticated with API token\n`);
88-
stdout.write(` Logged in as: ${muted(formatUserIdentity(user))}\n`);
85+
if (user) {
86+
stdout.write(` Logged in as: ${muted(formatUserIdentity(user))}\n`);
87+
}
8988
stdout.write(` Config saved to: ${getDbPath()}\n`);
9089
return;
9190
}

src/commands/auth/token.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* sentry auth token
3+
*
4+
* Print the stored authentication token (unmasked).
5+
* Useful for piping to other commands or scripts.
6+
*/
7+
8+
import { buildCommand } from "@stricli/core";
9+
import type { SentryContext } from "../../context.js";
10+
import { getAuthToken } from "../../lib/db/auth.js";
11+
import { AuthError } from "../../lib/errors.js";
12+
13+
export const tokenCommand = buildCommand({
14+
docs: {
15+
brief: "Print the stored authentication token",
16+
fullDescription:
17+
"Print the stored authentication token to stdout.\n\n" +
18+
"This outputs the raw token without any formatting, making it suitable for " +
19+
"piping to other commands or scripts. The token is printed without a trailing newline " +
20+
"when stdout is not a TTY (e.g., when piped).",
21+
},
22+
parameters: {},
23+
func(this: SentryContext): void {
24+
const { stdout } = this;
25+
26+
const token = getAuthToken();
27+
if (!token) {
28+
throw new AuthError("not_authenticated");
29+
}
30+
31+
// Add newline only if stdout is a TTY (interactive terminal)
32+
// When piped, omit newline for cleaner output
33+
const suffix = process.stdout.isTTY ? "\n" : "";
34+
stdout.write(`${token}${suffix}`);
35+
},
36+
});

src/lib/api-client.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,9 @@ export function triggerSolutionPlanning(
991991
/**
992992
* Get the currently authenticated user's information.
993993
* Used for setting user context in telemetry.
994+
*
995+
* Note: This endpoint may not work with OAuth App tokens, but works with
996+
* manually created API tokens. Callers should handle failures gracefully.
994997
*/
995998
export function getCurrentUser(): Promise<SentryUser> {
996999
return apiRequest<SentryUser>("/users/me/", {

src/lib/version-check.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
2222
const JITTER_FACTOR = 0.2;
2323

2424
/** Commands/flags that should not show update notifications */
25-
const SUPPRESSED_ARGS = new Set(["upgrade", "--version", "-V", "--json"]);
25+
const SUPPRESSED_ARGS = new Set([
26+
"upgrade",
27+
"--version",
28+
"-V",
29+
"--json",
30+
"token",
31+
]);
2632

2733
/** AbortController for pending version check fetch */
2834
let pendingAbortController: AbortController | null = null;

0 commit comments

Comments
 (0)