mid.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import Connect from 'connect';
  2. import { ServerResponse } from 'node:http';
  3. import https from 'https';
  4. import { parse as parseCookie } from 'cookie';
  5. import { sendReLogin } from '../SSE/server';
  6. import casdoorServer from 'casdoor-nodejs-sdk';
  7. import {
  8. ServerUrl,
  9. ClientId,
  10. SecretKey,
  11. Certificate,
  12. AppName,
  13. OrganizationName
  14. } from './config';
  15. const sdk = new casdoorServer.SDK({
  16. endpoint: ServerUrl,
  17. clientId: ClientId,
  18. clientSecret: SecretKey,
  19. certificate: Certificate,
  20. orgName: OrganizationName,
  21. appName: AppName
  22. }, {
  23. httpsAgent: new https.Agent({ rejectUnauthorized: false })
  24. })
  25. // 未授权响应
  26. function resUnauthorized(res: ServerResponse<Connect.IncomingMessage>, cookies?: Record<string, string | undefined>) {
  27. res.statusCode = 401; // Unauthorized
  28. res.setHeader('Content-Type', 'text/plain')
  29. res.end('Unauthorized')
  30. if (cookies) {
  31. const session_id = cookies['session_id']
  32. if (session_id) {
  33. // 重新登录
  34. setTimeout(() => {
  35. sendReLogin(session_id);
  36. }, 3000);
  37. }
  38. }
  39. }
  40. // 处理登录请求
  41. async function handleSignin(url: URL): Promise<{ access_token: string; refresh_token: string; } | undefined> {
  42. const code = url.searchParams.get('code');
  43. const state = url.searchParams.get('state');
  44. if (code && state) {
  45. const token = await sdk.getAuthToken(code);
  46. return token
  47. }
  48. return undefined
  49. }
  50. // 验证Token
  51. function verifyToken(token: string): boolean {
  52. try {
  53. sdk.parseJwtToken(token);
  54. return true;
  55. } catch (err) {
  56. console.error("token verify failed.");
  57. console.error(err);
  58. }
  59. return false;
  60. }
  61. // 刷新Token
  62. async function refreshToken(refresh_token: string) {
  63. const tokenGroup = await sdk.refreshToken(refresh_token).catch(err => {
  64. console.error("refresh token failed.")
  65. console.error(err)
  66. return undefined
  67. })
  68. return tokenGroup;
  69. }
  70. export default async function AuthMid(req: Connect.IncomingMessage, res: ServerResponse<Connect.IncomingMessage>, next: Connect.NextFunction) {
  71. const BaseUrl = process.env.BASE_URL
  72. if (req.url) {
  73. const url = new URL(req.url, "https://raspberrypi.dev");
  74. // 请求保护资源,验证JWT
  75. if (!url.pathname.startsWith(BaseUrl + 'casdoor') && url.pathname.endsWith('.md')) {
  76. console.log("verify JWT")
  77. const cookieStr = req.headers.cookie
  78. if (!cookieStr) {
  79. resUnauthorized(res);
  80. return
  81. } else {
  82. const cookies = parseCookie(cookieStr);
  83. console.log(cookies)
  84. const token = cookies['token'];
  85. const refresh_token = cookies['refresh_token'];
  86. // 验证Token
  87. if (token && verifyToken(token)) {
  88. // 验证成功
  89. await next();
  90. return
  91. }
  92. // 验证刷新Token
  93. if (refresh_token && verifyToken(refresh_token)) {
  94. // 刷新Token
  95. const tokenGroup = await refreshToken(refresh_token);
  96. if (tokenGroup) {
  97. // 刷新成功,设置token
  98. res.setHeader('Set-Cookie', [
  99. `token=${tokenGroup.access_token}; HttpOnly; Path=/; Max-Age=86400; SameSite=Strict`,
  100. `refresh_token=${tokenGroup.refresh_token}; HttpOnly; Path=/; SameSite=Strict`
  101. ]);
  102. await next();
  103. return
  104. }
  105. }
  106. resUnauthorized(res, cookies);
  107. return
  108. }
  109. }
  110. // 处理登录查询
  111. if (url.pathname.startsWith(BaseUrl + 'api/islogin')) {
  112. const cookieStr = req.headers.cookie
  113. if (cookieStr) {
  114. const cookies = parseCookie(cookieStr);
  115. const token = cookies['token'];
  116. const refresh_token = cookies['refresh_token'];
  117. // 验证Token
  118. if (token && verifyToken(token)) {
  119. // 验证成功
  120. res.statusCode = 200;
  121. res.setHeader('Content-Type', 'text/plain')
  122. res.end(token)
  123. return
  124. }
  125. // 验证刷新Token
  126. if (refresh_token && verifyToken(refresh_token)) {
  127. // 刷新Token
  128. const tokenGroup = await refreshToken(refresh_token);
  129. if (tokenGroup) {
  130. // 刷新成功,设置token
  131. res.setHeader('Set-Cookie', [
  132. `token=${tokenGroup.access_token}; HttpOnly; Path=/; Max-Age=86400; SameSite=Strict`,
  133. `refresh_token=${tokenGroup.refresh_token}; HttpOnly; Path=/; SameSite=Strict`
  134. ]);
  135. res.statusCode = 200;
  136. res.setHeader('Content-Type', 'text/plain')
  137. res.end(token)
  138. return
  139. }
  140. }
  141. }
  142. resUnauthorized(res);
  143. return
  144. }
  145. // 处理登录请求
  146. if (url.pathname.startsWith(BaseUrl + 'api/signin')) {
  147. const signinToken = await handleSignin(url).catch((err) => {
  148. console.log("handleSignin: ", err)
  149. });
  150. console.log("signinToken: ", signinToken)
  151. if (signinToken) {
  152. res.setHeader('Set-Cookie', [
  153. `token=${signinToken.access_token}; HttpOnly; Path=/; Max-Age=86400; SameSite=Strict`,
  154. `refresh_token=${signinToken.refresh_token}; HttpOnly; Path=/; SameSite=Strict`
  155. ])
  156. res.statusCode = 200;
  157. res.setHeader('Content-Type', 'application/json')
  158. res.end(JSON.stringify(signinToken))
  159. return
  160. }
  161. }
  162. // 处理登录请求
  163. if (url.pathname.startsWith(BaseUrl + 'api/exit')) {
  164. console.log("exit")
  165. // 清除token
  166. res.setHeader('Set-Cookie', [
  167. `token=; HttpOnly; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Strict`,
  168. `refresh_token=; HttpOnly; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Strict`
  169. ])
  170. res.statusCode = 200;
  171. res.setHeader('Content-Type', 'text/plain')
  172. res.end("Exit successful.")
  173. return
  174. }
  175. }
  176. // 继续执行下一个中间件
  177. await next();
  178. }