oauth.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.OAuth2Flow = void 0;
  4. const tslib_1 = require("tslib");
  5. const utils_fs_1 = require("@ionic/utils-fs");
  6. const utils_network_1 = require("@ionic/utils-network");
  7. const crypto = tslib_1.__importStar(require("crypto"));
  8. const http = tslib_1.__importStar(require("http"));
  9. const path = tslib_1.__importStar(require("path"));
  10. const qs = tslib_1.__importStar(require("querystring"));
  11. const constants_1 = require("../../constants");
  12. const errors_1 = require("../errors");
  13. const http_1 = require("../http");
  14. const open_1 = require("../open");
  15. const REDIRECT_PORT = 8123;
  16. const REDIRECT_HOST = 'localhost';
  17. class OAuth2Flow {
  18. constructor({ redirectHost = REDIRECT_HOST, redirectPort = REDIRECT_PORT, accessTokenRequestContentType = "application/json" /* ContentType.JSON */ }, e) {
  19. this.e = e;
  20. this.oauthConfig = this.getAuthConfig();
  21. this.redirectHost = redirectHost;
  22. this.redirectPort = redirectPort;
  23. this.accessTokenRequestContentType = accessTokenRequestContentType;
  24. }
  25. get redirectUrl() {
  26. return `http://${this.redirectHost}:${this.redirectPort}`;
  27. }
  28. async run() {
  29. const verifier = this.generateVerifier();
  30. const challenge = this.generateChallenge(verifier);
  31. const authorizationParams = this.generateAuthorizationParameters(challenge);
  32. const authorizationUrl = `${this.oauthConfig.authorizationUrl}?${qs.stringify(authorizationParams)}`;
  33. await (0, open_1.openUrl)(authorizationUrl);
  34. const { code, state } = await this.getAuthorizationCode();
  35. const token = await this.exchangeAuthForAccessToken(code, verifier);
  36. token.state = state;
  37. return token;
  38. }
  39. async exchangeRefreshToken(refreshToken) {
  40. const params = this.generateRefreshTokenParameters(refreshToken);
  41. const { req } = await this.e.client.make('POST', this.oauthConfig.tokenUrl, this.accessTokenRequestContentType);
  42. const res = await req.send(params);
  43. // check the response status code first here
  44. if (!res.ok) {
  45. throw new errors_1.FatalException('API request to refresh token was not successful.\n' +
  46. 'Please try to login again.\n' +
  47. (0, http_1.formatResponseError)(req, res.status));
  48. }
  49. if (!this.checkValidExchangeTokenRes(res)) {
  50. throw new errors_1.FatalException('API request was successful, but the refreshed token was unrecognized.\n' +
  51. 'Please try to login again.\n');
  52. }
  53. return res.body;
  54. }
  55. async getSuccessHtml() {
  56. const p = path.resolve(constants_1.ASSETS_DIRECTORY, 'oauth', 'success', 'index.html');
  57. const contents = await (0, utils_fs_1.readFile)(p, { encoding: 'utf8' });
  58. return contents;
  59. }
  60. async getAuthorizationCode() {
  61. if (!(await (0, utils_network_1.isPortAvailable)(this.redirectPort))) {
  62. throw new Error(`Cannot start local server. Port ${this.redirectPort} is in use.`);
  63. }
  64. const successHtml = await this.getSuccessHtml();
  65. return new Promise((resolve, reject) => {
  66. const server = http.createServer((req, res) => {
  67. if (req.url) {
  68. const params = qs.parse(req.url.substring(req.url.indexOf('?') + 1));
  69. if (params.code) {
  70. res.writeHead(200, { 'Content-Type': "text/html" /* ContentType.HTML */ });
  71. res.end(successHtml);
  72. req.socket.destroy();
  73. server.close();
  74. const authResult = {
  75. code: Array.isArray(params.code) ? params.code[0] : params.code,
  76. state: params.state ? (Array.isArray(params.state) ? decodeURI(params.state[0]) : decodeURI(params.state)) : '',
  77. };
  78. resolve(authResult);
  79. }
  80. // TODO, timeout, error handling
  81. }
  82. });
  83. server.listen(this.redirectPort, this.redirectHost);
  84. });
  85. }
  86. async exchangeAuthForAccessToken(authorizationCode, verifier) {
  87. const params = this.generateTokenParameters(authorizationCode, verifier);
  88. const { req } = await this.e.client.make('POST', this.oauthConfig.tokenUrl, this.accessTokenRequestContentType);
  89. const res = await req.send(params);
  90. if (!this.checkValidExchangeTokenRes(res)) {
  91. throw new errors_1.FatalException('API request was successful, but the response format was unrecognized.\n' +
  92. (0, http_1.formatResponseError)(req, res.status));
  93. }
  94. return res.body;
  95. }
  96. generateVerifier() {
  97. return this.base64URLEncode(crypto.randomBytes(32));
  98. }
  99. generateChallenge(verifier) {
  100. return this.base64URLEncode(crypto.createHash('sha256').update(verifier).digest());
  101. }
  102. base64URLEncode(buffer) {
  103. return buffer.toString('base64')
  104. .replace(/\+/g, '-')
  105. .replace(/\//g, '_')
  106. .replace(/=/g, '');
  107. }
  108. }
  109. exports.OAuth2Flow = OAuth2Flow;