-
-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathstatus.ts
More file actions
207 lines (189 loc) · 5.66 KB
/
status.ts
File metadata and controls
207 lines (189 loc) · 5.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/**
* sentry auth status
*
* Display authentication status and verify credentials.
*/
import type { SentryContext } from "../../context.js";
import { listOrganizationsUncached } from "../../lib/api-client.js";
import { buildCommand } from "../../lib/command.js";
import {
type AuthConfig,
type AuthSource,
ENV_SOURCE_PREFIX,
getAuthConfig,
isAuthenticated,
} from "../../lib/db/auth.js";
import {
getDefaultOrganization,
getDefaultProject,
} from "../../lib/db/defaults.js";
import { getDbPath } from "../../lib/db/index.js";
import { getUserInfo } from "../../lib/db/user.js";
import { AuthError, stringifyUnknown } from "../../lib/errors.js";
import { formatAuthStatus, maskToken } from "../../lib/formatters/human.js";
import { CommandOutput } from "../../lib/formatters/output.js";
import {
applyFreshFlag,
FRESH_ALIASES,
FRESH_FLAG,
} from "../../lib/list-command.js";
type StatusFlags = {
readonly "show-token": boolean;
readonly json: boolean;
readonly fresh: boolean;
readonly fields?: string[];
};
/** Check if the auth source is an environment variable */
function isEnvSource(source: AuthSource): boolean {
return source.startsWith(ENV_SOURCE_PREFIX);
}
/**
* Structured data representing the full auth status.
* Serves as both the JSON output shape and input to the human formatter.
*/
export type AuthStatusData = {
/** Whether the user is currently authenticated */
authenticated: boolean;
/** Auth source: "oauth" or "env:SENTRY_AUTH_TOKEN" etc. */
source: string;
/** Path to the SQLite config database (only for non-env tokens) */
configPath?: string;
/** User identity from cached user info */
user?: { name?: string; email?: string; username?: string };
/** Token display and metadata */
token?: {
/** Masked or full token string depending on --show-token */
display: string;
/** Expiration timestamp (ms since epoch), if available */
expiresAt?: number;
/** Whether auto-refresh via refresh token is enabled */
refreshEnabled: boolean;
};
/** Default org/project settings */
defaults?: {
organization?: string;
project?: string;
};
/** Credential verification results */
verification?: {
/** Whether the API call succeeded */
success: boolean;
/** Organizations accessible with the current token */
organizations?: Array<{ name: string; slug: string }>;
/** Error message if verification failed */
error?: string;
};
};
/**
* Collect token information into the data structure.
*/
function collectTokenInfo(
auth: AuthConfig | undefined,
showToken: boolean
): AuthStatusData["token"] | undefined {
if (!auth?.token) {
return;
}
const display = showToken ? auth.token : maskToken(auth.token);
const fromEnv = isEnvSource(auth.source);
return {
display,
// Env var tokens have no expiry or refresh
expiresAt: fromEnv ? undefined : auth.expiresAt,
refreshEnabled: fromEnv ? false : Boolean(auth.refreshToken),
};
}
/**
* Collect default org/project into the data structure.
*/
function collectDefaults(): AuthStatusData["defaults"] {
const org = getDefaultOrganization();
const project = getDefaultProject();
if (!(org || project)) {
return;
}
return {
organization: org ?? undefined,
project: project ?? undefined,
};
}
/**
* Verify credentials by fetching organizations.
* Captures success/failure into data rather than throwing.
*/
async function verifyCredentials(): Promise<AuthStatusData["verification"]> {
try {
const orgs = await listOrganizationsUncached();
return {
success: true,
organizations: orgs.map((o) => ({ name: o.name, slug: o.slug })),
};
} catch (err) {
return {
success: false,
error: stringifyUnknown(err),
};
}
}
export const statusCommand = buildCommand({
auth: false,
docs: {
brief: "View authentication status",
fullDescription:
"Display information about your current authentication status, " +
"including whether you're logged in and your default organization/project settings.",
},
output: { human: formatAuthStatus },
parameters: {
flags: {
"show-token": {
kind: "boolean",
brief: "Show the stored token (masked by default)",
default: false,
},
fresh: FRESH_FLAG,
},
aliases: FRESH_ALIASES,
},
async *func(this: SentryContext, flags: StatusFlags) {
applyFreshFlag(flags);
const auth = getAuthConfig();
const authenticated = isAuthenticated();
const fromEnv = auth ? isEnvSource(auth.source) : false;
if (!authenticated) {
// Skip auto-login - user explicitly ran status to check auth state
throw new AuthError("not_authenticated", undefined, {
skipAutoAuth: true,
});
}
// Env var tokens may belong to a different user/instance than the cached
// user_info (populated by a prior login or whoami). Skip the cache for env
// sources to avoid displaying stale identity.
let user: AuthStatusData["user"];
if (!fromEnv) {
const userInfo = getUserInfo();
user = userInfo
? {
name: userInfo.name,
email: userInfo.email,
username: userInfo.username,
}
: undefined;
}
const data: AuthStatusData = {
authenticated: true,
source: auth?.source ?? "oauth",
configPath: fromEnv ? undefined : getDbPath(),
user,
token: collectTokenInfo(auth, flags["show-token"]),
defaults: collectDefaults(),
verification: await verifyCredentials(),
};
yield new CommandOutput(data);
if (fromEnv) {
return {
hint: "Run `sentry auth whoami` to see which user this token belongs to",
};
}
},
});