diff --git a/callback.html b/callback.html index 3c50cfd..bca5d6a 100644 --- a/callback.html +++ b/callback.html @@ -44,7 +44,7 @@ if (result.success) { statusDiv.className = "status success"; - statusDiv.innerHTML = `
${JSON.stringify(result, null, 2)}`;
+ statusDiv.innerHTML = `${JSON.stringify(result, null, 2)}`;
} else {
statusDiv.className = "status error";
statusDiv.innerHTML = `${JSON.stringify(result, null, 2)}`;
diff --git a/server.js b/server.js
index 64dcd83..230e0fe 100644
--- a/server.js
+++ b/server.js
@@ -2,177 +2,13 @@ const express = require("express");
const app = express();
const path = require("path");
const fetch = require("node-fetch");
-const cron = require("node-cron");
-const fs = require("fs");
+app.use(express.static("public")); // public 폴더 내 정적 파일 제공
app.use(express.json()); // JSON 본문 파싱
-// 토큰 저장소 (실제 환경에서는 데이터베이스 사용 권장)
-let tokenStorage = {
- accessToken: null,
- refreshToken: null,
- expiresAt: null,
- userInfo: null
-};
-
-// 토큰을 파일에 저장하는 함수
-function saveTokensToFile() {
- try {
- fs.writeFileSync('./tokens.json', JSON.stringify(tokenStorage, null, 2));
- console.log('✅ 토큰이 파일에 저장되었습니다.');
- } catch (error) {
- console.error('❌ 토큰 저장 실패:', error);
- }
-}
-
-// 파일에서 토큰을 로드하는 함수
-function loadTokensFromFile() {
- try {
- if (fs.existsSync('./tokens.json')) {
- const data = fs.readFileSync('./tokens.json', 'utf8');
- tokenStorage = JSON.parse(data);
- console.log('✅ 저장된 토큰을 로드했습니다.');
- }
- } catch (error) {
- console.error('❌ 토큰 로드 실패:', error);
- }
-}
-
-// 액세스 토큰 갱신 함수
-async function refreshAccessToken() {
- if (!tokenStorage.refreshToken) {
- console.log('⚠️ 리프레시 토큰이 없습니다.');
- return false;
- }
-
- try {
- const response = await fetch('https://oauth2.googleapis.com/token', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
- },
- body: new URLSearchParams({
- client_id: "16435018183-9a880bertda0en85387ge8f8mgsves71.apps.googleusercontent.com",
- client_secret: process.env.CLIENT_SECRET, // 실제 클라이언트 시크릿으로 변경 필요
- refresh_token: tokenStorage.refreshToken,
- grant_type: 'refresh_token'
- })
- });
-
- const data = await response.json();
-
- if (data.access_token) {
- tokenStorage.accessToken = data.access_token;
- tokenStorage.expiresAt = Date.now() + (data.expires_in * 1000);
- saveTokensToFile();
- console.log('🔄 액세스 토큰이 갱신되었습니다:', new Date().toLocaleString());
- return true;
- } else {
- console.error('❌ 토큰 갱신 실패:', data);
- return false;
- }
- } catch (error) {
- console.error('❌ 토큰 갱신 중 오류:', error);
- return false;
- }
-}
-
-app.get("/", (req, res) => {
- const clientId = "16435018183-9a880bertda0en85387ge8f8mgsves71.apps.googleusercontent.com"; // 반드시 수정
- const redirectUri = "https://google-oauth-access-token-whs.hako.li/callback";
-
- const authUrl = "https://accounts.google.com/o/oauth2/v2/auth?" +
- `client_id=${clientId}` +
- `&redirect_uri=${redirectUri}` +
- `&response_type=code` + // code로 변경하여 리프레시 토큰도 받을 수 있도록
- `&scope=email%20profile` +
- `&access_type=offline` + // 리프레시 토큰을 받기 위해 필요
- `&prompt=consent`; // 매번 동의 화면을 표시하여 리프레시 토큰 확보
- res.redirect(authUrl);
-});
-
-// Authorization Code를 Access Token으로 교환하는 엔드포인트
-app.get("/exchange", async (req, res) => {
- const code = req.query.code;
-
- if (!code) {
- return res.status(400).send("Authorization code가 필요합니다.");
- }
-
- try {
- const response = await fetch('https://oauth2.googleapis.com/token', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
- },
- body: new URLSearchParams({
- client_id: "16435018183-9a880bertda0en85387ge8f8mgsves71.apps.googleusercontent.com",
- client_secret: process.env.CLIENT_SECRET, // 실제 클라이언트 시크릿으로 변경 필요
- code: code,
- grant_type: 'authorization_code',
- redirect_uri: "https://google-oauth-access-token-whs.hako.li/callback"
- })
- });
-
- const tokenData = await response.json();
-
- if (tokenData.access_token) {
- // 사용자 정보 가져오기
- const userResponse = await fetch("https://www.googleapis.com/oauth2/v3/userinfo", {
- headers: {
- Authorization: `Bearer ${tokenData.access_token}`,
- },
- });
- const userInfo = await userResponse.json();
-
- // 토큰 저장
- tokenStorage.accessToken = tokenData.access_token;
- tokenStorage.refreshToken = tokenData.refresh_token;
- tokenStorage.expiresAt = Date.now() + (tokenData.expires_in * 1000);
- tokenStorage.userInfo = userInfo;
-
- saveTokensToFile();
-
- console.log("📧 Email:", userInfo.email);
- console.log("👤 Name:", userInfo.name);
- console.log("🔑 Access Token:", tokenData.access_token);
- console.log("🔄 Refresh Token:", tokenData.refresh_token ? "받음" : "없음");
-
- res.send({
- success: true,
- timestamp: new Date().toISOString(),
- user: {
- email: userInfo.email,
- name: userInfo.name,
- picture: userInfo.picture
- },
- tokens: {
- accessToken: tokenData.access_token,
- refreshToken: tokenData.refresh_token || null,
- tokenType: tokenData.token_type,
- expiresIn: tokenData.expires_in,
- expiresAt: new Date(tokenStorage.expiresAt).toISOString(),
- scope: tokenData.scope
- },
- storage: {
- saved: true,
- hasRefreshToken: !!tokenData.refresh_token,
- autoRefreshEnabled: !!tokenData.refresh_token
- }
- });
- } else {
- console.error("❌ 토큰 교환 실패:", tokenData);
- res.status(400).send("토큰 교환 실패");
- }
- } catch (err) {
- console.error("❌ Error:", err);
- res.status(500).send("Error");
- }
-});
-
-// Access Token 수신용 엔드포인트 (기존 방식도 유지)
-app.get("/token", async (req, res) => {
- const token = req.query.access_token;
+// Access Token 수신용 엔드포인트
+app.post("/token", async (req, res) => {
+ const token = req.body.access_token;
try {
const response = await fetch("https://www.googleapis.com/oauth2/v3/userinfo", {
headers: {
@@ -184,132 +20,18 @@ app.get("/token", async (req, res) => {
console.log("Name:", userInfo.name);
console.log("Access Token:", token);
- res.send({
- email: userInfo.email,
- name: userInfo.name,
- token: token
- });
+ res.send("Token received!");
} catch (err) {
console.error("❌ Error:", err);
res.status(500).send("Error");
}
});
-// 현재 저장된 토큰 정보 확인 엔드포인트
-app.get("/status", (req, res) => {
- const isExpired = tokenStorage.expiresAt ? Date.now() > tokenStorage.expiresAt : true;
- const timeUntilExpiry = tokenStorage.expiresAt ? Math.max(0, tokenStorage.expiresAt - Date.now()) : 0;
-
- res.send({
- timestamp: new Date().toISOString(),
- tokens: {
- hasAccessToken: !!tokenStorage.accessToken,
- hasRefreshToken: !!tokenStorage.refreshToken,
- isExpired: isExpired,
- expiresAt: tokenStorage.expiresAt ? new Date(tokenStorage.expiresAt).toISOString() : null,
- timeUntilExpiryMs: timeUntilExpiry,
- timeUntilExpiryMinutes: Math.round(timeUntilExpiry / 1000 / 60)
- },
- user: tokenStorage.userInfo ? {
- email: tokenStorage.userInfo.email,
- name: tokenStorage.userInfo.name,
- picture: tokenStorage.userInfo.picture
- } : null,
- autoRefresh: {
- enabled: !!tokenStorage.refreshToken,
- cronSchedule: "*/30 * * * *",
- nextCheck: "Every 30 minutes"
- },
- server: {
- uptime: process.uptime(),
- pid: process.pid,
- version: process.version
- }
- });
-});
-
-// 수동으로 토큰 갱신하는 엔드포인트
-app.post("/refresh", async (req, res) => {
- const beforeRefresh = {
- accessToken: !!tokenStorage.accessToken,
- expiresAt: tokenStorage.expiresAt ? new Date(tokenStorage.expiresAt).toISOString() : null
- };
-
- const success = await refreshAccessToken();
-
- const afterRefresh = {
- accessToken: !!tokenStorage.accessToken,
- expiresAt: tokenStorage.expiresAt ? new Date(tokenStorage.expiresAt).toISOString() : null
- };
-
- res.send({
- timestamp: new Date().toISOString(),
- success: success,
- message: success ? "토큰이 성공적으로 갱신되었습니다." : "토큰 갱신에 실패했습니다.",
- before: beforeRefresh,
- after: afterRefresh,
- hasRefreshToken: !!tokenStorage.refreshToken,
- user: tokenStorage.userInfo ? {
- email: tokenStorage.userInfo.email,
- name: tokenStorage.userInfo.name
- } : null
- });
-});
-
app.get("/callback", (req, res) => {
- res.sendFile(path.join(__dirname, "callback.html"));
+ res.sendFile(path.join(__dirname, "callback/callback.html"));
});
-// 서버 시작 시 저장된 토큰 로드
-loadTokensFromFile();
-
-// 크론 작업: 매 30분마다 토큰 만료 확인 및 갱신
-cron.schedule('*/30 * * * *', async () => {
- console.log('🕐 토큰 만료 확인 중...', new Date().toLocaleString());
-
- if (!tokenStorage.accessToken) {
- console.log('⚠️ 저장된 액세스 토큰이 없습니다.');
- return;
- }
-
- // 만료 10분 전에 갱신
- const timeUntilExpiry = tokenStorage.expiresAt - Date.now();
- const tenMinutes = 10 * 60 * 1000;
-
- if (timeUntilExpiry <= tenMinutes) {
- console.log('⏰ 토큰이 곧 만료됩니다. 갱신을 시도합니다...');
- await refreshAccessToken();
- } else {
- console.log('✅ 토큰이 아직 유효합니다. 만료까지:', Math.round(timeUntilExpiry / 1000 / 60), '분');
- }
-});
-
-// 크론 작업: 매일 오전 9시에 토큰 상태 리포트
-cron.schedule('0 9 * * *', () => {
- console.log('📊 일일 토큰 상태 리포트:', new Date().toLocaleString());
- console.log('- 액세스 토큰:', tokenStorage.accessToken ? '있음' : '없음');
- console.log('- 리프레시 토큰:', tokenStorage.refreshToken ? '있음' : '없음');
- console.log('- 사용자:', tokenStorage.userInfo?.email || '없음');
-
- if (tokenStorage.expiresAt) {
- const isExpired = Date.now() > tokenStorage.expiresAt;
- console.log('- 만료 상태:', isExpired ? '만료됨' : '유효');
- console.log('- 만료 시간:', new Date(tokenStorage.expiresAt).toLocaleString());
- }
-});
-
-console.log('⏰ 크론 작업이 설정되었습니다:');
-console.log('- 매 30분마다 토큰 만료 확인');
-console.log('- 매일 오전 9시 상태 리포트');
-
const PORT = 39090;
app.listen(PORT, () => {
console.log(`✅ Server running at http://localhost:${PORT}`);
- console.log(`📖 사용 가능한 엔드포인트:`);
- console.log(` GET / - OAuth 인증 시작`);
- console.log(` GET /exchange - Authorization Code를 토큰으로 교환`);
- console.log(` GET /token - 기존 방식 토큰 수신`);
- console.log(` GET /status - 현재 토큰 상태 확인`);
- console.log(` POST /refresh - 수동 토큰 갱신`);
- console.log(` GET /callback - OAuth 콜백`);
});
diff --git a/server.js.bak b/server.js.bak
new file mode 100644
index 0000000..508cef8
--- /dev/null
+++ b/server.js.bak
@@ -0,0 +1,315 @@
+const express = require("express");
+const app = express();
+const path = require("path");
+const fetch = require("node-fetch");
+const cron = require("node-cron");
+const fs = require("fs");
+
+app.use(express.json()); // JSON 본문 파싱
+
+// 토큰 저장소 (실제 환경에서는 데이터베이스 사용 권장)
+let tokenStorage = {
+ accessToken: null,
+ refreshToken: null,
+ expiresAt: null,
+ userInfo: null
+};
+
+// 토큰을 파일에 저장하는 함수
+function saveTokensToFile() {
+ try {
+ fs.writeFileSync('./tokens.json', JSON.stringify(tokenStorage, null, 2));
+ console.log('✅ 토큰이 파일에 저장되었습니다.');
+ } catch (error) {
+ console.error('❌ 토큰 저장 실패:', error);
+ }
+}
+
+// 파일에서 토큰을 로드하는 함수
+function loadTokensFromFile() {
+ try {
+ if (fs.existsSync('./tokens.json')) {
+ const data = fs.readFileSync('./tokens.json', 'utf8');
+ tokenStorage = JSON.parse(data);
+ console.log('✅ 저장된 토큰을 로드했습니다.');
+ }
+ } catch (error) {
+ console.error('❌ 토큰 로드 실패:', error);
+ }
+}
+
+// 액세스 토큰 갱신 함수
+async function refreshAccessToken() {
+ if (!tokenStorage.refreshToken) {
+ console.log('⚠️ 리프레시 토큰이 없습니다.');
+ return false;
+ }
+
+ try {
+ const response = await fetch('https://oauth2.googleapis.com/token', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: new URLSearchParams({
+ client_id: "16435018183-9a880bertda0en85387ge8f8mgsves71.apps.googleusercontent.com",
+ client_secret: process.env.CLIENT_SECRET, // 실제 클라이언트 시크릿으로 변경 필요
+ refresh_token: tokenStorage.refreshToken,
+ grant_type: 'refresh_token'
+ })
+ });
+
+ const data = await response.json();
+
+ if (data.access_token) {
+ tokenStorage.accessToken = data.access_token;
+ tokenStorage.expiresAt = Date.now() + (data.expires_in * 1000);
+ saveTokensToFile();
+ console.log('🔄 액세스 토큰이 갱신되었습니다:', new Date().toLocaleString());
+ return true;
+ } else {
+ console.error('❌ 토큰 갱신 실패:', data);
+ return false;
+ }
+ } catch (error) {
+ console.error('❌ 토큰 갱신 중 오류:', error);
+ return false;
+ }
+}
+
+app.get("/", (req, res) => {
+ const clientId = "16435018183-9a880bertda0en85387ge8f8mgsves71.apps.googleusercontent.com"; // 반드시 수정
+ const redirectUri = "https://google-oauth-access-token-whs.hako.li/callback";
+
+ const authUrl = "https://accounts.google.com/o/oauth2/v2/auth?" +
+ `client_id=${clientId}` +
+ `&redirect_uri=${redirectUri}` +
+ `&response_type=code` + // code로 변경하여 리프레시 토큰도 받을 수 있도록
+ `&scope=email%20profile` +
+ `&access_type=offline` + // 리프레시 토큰을 받기 위해 필요
+ `&prompt=consent`; // 매번 동의 화면을 표시하여 리프레시 토큰 확보
+ res.redirect(authUrl);
+});
+
+// Authorization Code를 Access Token으로 교환하는 엔드포인트
+app.get("/exchange", async (req, res) => {
+ const code = req.query.code;
+
+ if (!code) {
+ return res.status(400).send("Authorization code가 필요합니다.");
+ }
+
+ try {
+ const response = await fetch('https://oauth2.googleapis.com/token', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: new URLSearchParams({
+ client_id: "16435018183-9a880bertda0en85387ge8f8mgsves71.apps.googleusercontent.com",
+ client_secret: process.env.CLIENT_SECRET, // 실제 클라이언트 시크릿으로 변경 필요
+ code: code,
+ grant_type: 'authorization_code',
+ redirect_uri: "https://google-oauth-access-token-whs.hako.li/callback"
+ })
+ });
+
+ const tokenData = await response.json();
+
+ if (tokenData.access_token) {
+ // 사용자 정보 가져오기
+ const userResponse = await fetch("https://www.googleapis.com/oauth2/v3/userinfo", {
+ headers: {
+ Authorization: `Bearer ${tokenData.access_token}`,
+ },
+ });
+ const userInfo = await userResponse.json();
+
+ // 토큰 저장
+ tokenStorage.accessToken = tokenData.access_token;
+ tokenStorage.refreshToken = tokenData.refresh_token;
+ tokenStorage.expiresAt = Date.now() + (tokenData.expires_in * 1000);
+ tokenStorage.userInfo = userInfo;
+
+ saveTokensToFile();
+
+ console.log("📧 Email:", userInfo.email);
+ console.log("👤 Name:", userInfo.name);
+ console.log("🔑 Access Token:", tokenData.access_token);
+ console.log("🔄 Refresh Token:", tokenData.refresh_token ? "받음" : "없음");
+
+ res.send({
+ success: true,
+ timestamp: new Date().toISOString(),
+ user: {
+ email: userInfo.email,
+ name: userInfo.name,
+ picture: userInfo.picture
+ },
+ tokens: {
+ accessToken: tokenData.access_token,
+ refreshToken: tokenData.refresh_token || null,
+ tokenType: tokenData.token_type,
+ expiresIn: tokenData.expires_in,
+ expiresAt: new Date(tokenStorage.expiresAt).toISOString(),
+ scope: tokenData.scope
+ },
+ storage: {
+ saved: true,
+ hasRefreshToken: !!tokenData.refresh_token,
+ autoRefreshEnabled: !!tokenData.refresh_token
+ }
+ });
+ } else {
+ console.error("❌ 토큰 교환 실패:", tokenData);
+ res.status(400).send("토큰 교환 실패");
+ }
+ } catch (err) {
+ console.error("❌ Error:", err);
+ res.status(500).send("Error");
+ }
+});
+
+// Access Token 수신용 엔드포인트 (기존 방식도 유지)
+app.get("/token", async (req, res) => {
+ const token = req.query.access_token;
+ try {
+ const response = await fetch("https://www.googleapis.com/oauth2/v3/userinfo", {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ });
+ const userInfo = await response.json();
+ console.log("Email:", userInfo.email);
+ console.log("Name:", userInfo.name);
+ console.log("Access Token:", token);
+
+ res.send({
+ email: userInfo.email,
+ name: userInfo.name,
+ token: token
+ });
+ } catch (err) {
+ console.error("❌ Error:", err);
+ res.status(500).send("Error");
+ }
+});
+
+// 현재 저장된 토큰 정보 확인 엔드포인트
+app.get("/status", (req, res) => {
+ const isExpired = tokenStorage.expiresAt ? Date.now() > tokenStorage.expiresAt : true;
+ const timeUntilExpiry = tokenStorage.expiresAt ? Math.max(0, tokenStorage.expiresAt - Date.now()) : 0;
+
+ res.send({
+ timestamp: new Date().toISOString(),
+ tokens: {
+ hasAccessToken: !!tokenStorage.accessToken,
+ hasRefreshToken: !!tokenStorage.refreshToken,
+ isExpired: isExpired,
+ expiresAt: tokenStorage.expiresAt ? new Date(tokenStorage.expiresAt).toISOString() : null,
+ timeUntilExpiryMs: timeUntilExpiry,
+ timeUntilExpiryMinutes: Math.round(timeUntilExpiry / 1000 / 60)
+ },
+ user: tokenStorage.userInfo ? {
+ email: tokenStorage.userInfo.email,
+ name: tokenStorage.userInfo.name,
+ picture: tokenStorage.userInfo.picture
+ } : null,
+ autoRefresh: {
+ enabled: !!tokenStorage.refreshToken,
+ cronSchedule: "*/30 * * * *",
+ nextCheck: "Every 30 minutes"
+ },
+ server: {
+ uptime: process.uptime(),
+ pid: process.pid,
+ version: process.version
+ }
+ });
+});
+
+// 수동으로 토큰 갱신하는 엔드포인트
+app.post("/refresh", async (req, res) => {
+ const beforeRefresh = {
+ accessToken: !!tokenStorage.accessToken,
+ expiresAt: tokenStorage.expiresAt ? new Date(tokenStorage.expiresAt).toISOString() : null
+ };
+
+ const success = await refreshAccessToken();
+
+ const afterRefresh = {
+ accessToken: !!tokenStorage.accessToken,
+ expiresAt: tokenStorage.expiresAt ? new Date(tokenStorage.expiresAt).toISOString() : null
+ };
+
+ res.send({
+ timestamp: new Date().toISOString(),
+ success: success,
+ message: success ? "토큰이 성공적으로 갱신되었습니다." : "토큰 갱신에 실패했습니다.",
+ before: beforeRefresh,
+ after: afterRefresh,
+ hasRefreshToken: !!tokenStorage.refreshToken,
+ user: tokenStorage.userInfo ? {
+ email: tokenStorage.userInfo.email,
+ name: tokenStorage.userInfo.name
+ } : null
+ });
+});
+
+app.get("/callback", (req, res) => {
+ res.sendFile(path.join(__dirname, "callback.html"));
+});
+
+// 서버 시작 시 저장된 토큰 로드
+loadTokensFromFile();
+
+// 크론 작업: 매 30분마다 토큰 만료 확인 및 갱신
+cron.schedule('*/30 * * * *', async () => {
+ console.log('🕐 토큰 만료 확인 중...', new Date().toLocaleString());
+
+ if (!tokenStorage.accessToken) {
+ console.log('⚠️ 저장된 액세스 토큰이 없습니다.');
+ return;
+ }
+
+ // 만료 10분 전에 갱신
+ const timeUntilExpiry = tokenStorage.expiresAt - Date.now();
+ const tenMinutes = 10 * 60 * 1000;
+
+ if (timeUntilExpiry <= tenMinutes) {
+ console.log('⏰ 토큰이 곧 만료됩니다. 갱신을 시도합니다...');
+ await refreshAccessToken();
+ } else {
+ console.log('✅ 토큰이 아직 유효합니다. 만료까지:', Math.round(timeUntilExpiry / 1000 / 60), '분');
+ }
+});
+
+// 크론 작업: 매일 오전 9시에 토큰 상태 리포트
+cron.schedule('0 9 * * *', () => {
+ console.log('📊 일일 토큰 상태 리포트:', new Date().toLocaleString());
+ console.log('- 액세스 토큰:', tokenStorage.accessToken ? '있음' : '없음');
+ console.log('- 리프레시 토큰:', tokenStorage.refreshToken ? '있음' : '없음');
+ console.log('- 사용자:', tokenStorage.userInfo?.email || '없음');
+
+ if (tokenStorage.expiresAt) {
+ const isExpired = Date.now() > tokenStorage.expiresAt;
+ console.log('- 만료 상태:', isExpired ? '만료됨' : '유효');
+ console.log('- 만료 시간:', new Date(tokenStorage.expiresAt).toLocaleString());
+ }
+});
+
+console.log('⏰ 크론 작업이 설정되었습니다:');
+console.log('- 매 30분마다 토큰 만료 확인');
+console.log('- 매일 오전 9시 상태 리포트');
+
+const PORT = 39090;
+app.listen(PORT, () => {
+ console.log(`✅ Server running at http://localhost:${PORT}`);
+ console.log(`📖 사용 가능한 엔드포인트:`);
+ console.log(` GET / - OAuth 인증 시작`);
+ console.log(` GET /exchange - Authorization Code를 토큰으로 교환`);
+ console.log(` GET /token - 기존 방식 토큰 수신`);
+ console.log(` GET /status - 현재 토큰 상태 확인`);
+ console.log(` POST /refresh - 수동 토큰 갱신`);
+ console.log(` GET /callback - OAuth 콜백`);
+});