@skillmarkdown/cli 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/commands/login.js +25 -10
- package/dist/lib/firebase-auth.js +36 -8
- package/dist/lib/github-device-flow.js +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,6 +94,8 @@ skillmd login --reauth
|
|
|
94
94
|
skillmd logout
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
When a saved session exists, `skillmd login` verifies the stored refresh token. If it is invalid/expired, the CLI automatically starts a new login flow. If verification is inconclusive (for example network timeout), the command exits non-zero and keeps the current session.
|
|
98
|
+
|
|
97
99
|
## Development
|
|
98
100
|
|
|
99
101
|
- Local testing guide (includes manual `login` auth checks): `docs/testing.md`
|
package/dist/commands/login.js
CHANGED
|
@@ -56,21 +56,36 @@ async function runLoginCommand(args, options = {}) {
|
|
|
56
56
|
}
|
|
57
57
|
const readSessionFn = options.readSession ?? auth_session_1.readAuthSession;
|
|
58
58
|
const writeSessionFn = options.writeSession ?? auth_session_1.writeAuthSession;
|
|
59
|
+
const clearSessionFn = options.clearSession ?? auth_session_1.clearAuthSession;
|
|
59
60
|
if (status) {
|
|
60
61
|
return printSessionStatus(readSessionFn());
|
|
61
62
|
}
|
|
62
|
-
const existingSession = readSessionFn();
|
|
63
|
-
if (existingSession && !reauth) {
|
|
64
|
-
if (existingSession.email) {
|
|
65
|
-
console.log(`Already logged in as ${existingSession.email}. Run 'skillmd logout' first.`);
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
console.log("Already logged in. Run 'skillmd logout' first.");
|
|
69
|
-
}
|
|
70
|
-
return 0;
|
|
71
|
-
}
|
|
72
63
|
try {
|
|
73
64
|
const config = requireConfig(options.env ?? process.env);
|
|
65
|
+
const existingSession = readSessionFn();
|
|
66
|
+
if (existingSession && !reauth) {
|
|
67
|
+
const verifyRefreshTokenFn = options.verifyRefreshToken ?? firebase_auth_1.verifyFirebaseRefreshToken;
|
|
68
|
+
try {
|
|
69
|
+
const validation = await verifyRefreshTokenFn(config.firebaseApiKey, existingSession.refreshToken);
|
|
70
|
+
if (validation.valid) {
|
|
71
|
+
if (existingSession.email) {
|
|
72
|
+
console.log(`Already logged in as ${existingSession.email}. Run 'skillmd logout' first.`);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
console.log("Already logged in. Run 'skillmd logout' first.");
|
|
76
|
+
}
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
clearSessionFn();
|
|
80
|
+
console.log("Existing session is no longer valid. Starting re-authentication.");
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
84
|
+
console.error(`skillmd login: unable to verify existing session (${message}). ` +
|
|
85
|
+
"Keeping current session. Run 'skillmd login --reauth' to force reauthentication.");
|
|
86
|
+
return 1;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
74
89
|
const requestDeviceCodeFn = options.requestDeviceCode ?? github_device_flow_1.requestDeviceCode;
|
|
75
90
|
const pollForAccessTokenFn = options.pollForAccessToken ?? github_device_flow_1.pollForAccessToken;
|
|
76
91
|
const signInFn = options.signInWithGitHubAccessToken ?? firebase_auth_1.signInWithGitHubAccessToken;
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.signInWithGitHubAccessToken = signInWithGitHubAccessToken;
|
|
4
|
+
exports.verifyFirebaseRefreshToken = verifyFirebaseRefreshToken;
|
|
4
5
|
const http_1 = require("./http");
|
|
5
6
|
const FIREBASE_HTTP_TIMEOUT_MS = 10000;
|
|
7
|
+
async function parseJsonApiResponse(response, apiLabel) {
|
|
8
|
+
const text = await response.text();
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(text);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
throw new Error(`${apiLabel} returned non-JSON response (${response.status})`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
6
16
|
async function signInWithGitHubAccessToken(apiKey, githubAccessToken) {
|
|
7
17
|
const response = await (0, http_1.fetchWithTimeout)(`https://identitytoolkit.googleapis.com/v1/accounts:signInWithIdp?key=${encodeURIComponent(apiKey)}`, {
|
|
8
18
|
method: "POST",
|
|
@@ -16,14 +26,7 @@ async function signInWithGitHubAccessToken(apiKey, githubAccessToken) {
|
|
|
16
26
|
postBody: `access_token=${encodeURIComponent(githubAccessToken)}&providerId=github.com`,
|
|
17
27
|
}),
|
|
18
28
|
}, { timeoutMs: FIREBASE_HTTP_TIMEOUT_MS });
|
|
19
|
-
const
|
|
20
|
-
let payload;
|
|
21
|
-
try {
|
|
22
|
-
payload = JSON.parse(text);
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
throw new Error(`Firebase auth API returned non-JSON response (${response.status})`);
|
|
26
|
-
}
|
|
29
|
+
const payload = await parseJsonApiResponse(response, "Firebase auth API");
|
|
27
30
|
if (!response.ok) {
|
|
28
31
|
const message = payload.error?.message || "Firebase signInWithIdp request failed";
|
|
29
32
|
throw new Error(`Firebase auth error: ${message}`);
|
|
@@ -37,3 +40,28 @@ async function signInWithGitHubAccessToken(apiKey, githubAccessToken) {
|
|
|
37
40
|
refreshToken: payload.refreshToken,
|
|
38
41
|
};
|
|
39
42
|
}
|
|
43
|
+
async function verifyFirebaseRefreshToken(apiKey, refreshToken) {
|
|
44
|
+
const response = await (0, http_1.fetchWithTimeout)(`https://securetoken.googleapis.com/v1/token?key=${encodeURIComponent(apiKey)}`, {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: {
|
|
47
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
48
|
+
},
|
|
49
|
+
body: new URLSearchParams({
|
|
50
|
+
grant_type: "refresh_token",
|
|
51
|
+
refresh_token: refreshToken,
|
|
52
|
+
}),
|
|
53
|
+
}, { timeoutMs: FIREBASE_HTTP_TIMEOUT_MS });
|
|
54
|
+
const payload = await parseJsonApiResponse(response, "Firebase token API");
|
|
55
|
+
if (response.ok) {
|
|
56
|
+
if (!payload.refresh_token || !payload.access_token) {
|
|
57
|
+
throw new Error("Firebase token API response was missing required fields");
|
|
58
|
+
}
|
|
59
|
+
return { valid: true };
|
|
60
|
+
}
|
|
61
|
+
const errorMessage = payload.error?.message ?? "";
|
|
62
|
+
if (response.status === 400 &&
|
|
63
|
+
(errorMessage === "INVALID_REFRESH_TOKEN" || errorMessage === "TOKEN_EXPIRED")) {
|
|
64
|
+
return { valid: false };
|
|
65
|
+
}
|
|
66
|
+
throw new Error(`Firebase token verification failed (${response.status}): ${errorMessage || "unknown error"}`);
|
|
67
|
+
}
|
|
@@ -53,11 +53,12 @@ function sleep(ms) {
|
|
|
53
53
|
setTimeout(resolve, ms);
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
|
-
async function pollForAccessToken(clientId, deviceCode, intervalSeconds, expiresInSeconds) {
|
|
56
|
+
async function pollForAccessToken(clientId, deviceCode, intervalSeconds, expiresInSeconds, options = {}) {
|
|
57
57
|
const startedAt = Date.now();
|
|
58
58
|
let pollInterval = Math.max(1, intervalSeconds);
|
|
59
|
+
const sleepFn = options.sleep ?? sleep;
|
|
59
60
|
while (Date.now() - startedAt < expiresInSeconds * 1000) {
|
|
60
|
-
await
|
|
61
|
+
await sleepFn(pollInterval * 1000);
|
|
61
62
|
const payload = await postGitHubForm(GITHUB_ACCESS_TOKEN_URL, new URLSearchParams({
|
|
62
63
|
client_id: clientId,
|
|
63
64
|
device_code: deviceCode,
|