session.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. "use strict";
  2. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
  3. if (k2 === undefined) k2 = k;
  4. var desc = Object.getOwnPropertyDescriptor(m, k);
  5. if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
  6. desc = { enumerable: true, get: function() { return m[k]; } };
  7. }
  8. Object.defineProperty(o, k2, desc);
  9. }) : (function(o, m, k, k2) {
  10. if (k2 === undefined) k2 = k;
  11. o[k2] = m[k];
  12. }));
  13. var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
  14. Object.defineProperty(o, "default", { enumerable: true, value: v });
  15. }) : function(o, v) {
  16. o["default"] = v;
  17. });
  18. var __importStar = (this && this.__importStar) || function (mod) {
  19. if (mod && mod.__esModule) return mod;
  20. var result = {};
  21. if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
  22. __setModuleDefault(result, mod);
  23. return result;
  24. };
  25. Object.defineProperty(exports, "__esModule", { value: true });
  26. exports.promptToSignup = exports.promptToLogin = exports.ProSession = exports.BaseSession = void 0;
  27. const guards_1 = require("../guards");
  28. const color_1 = require("./color");
  29. const errors_1 = require("./errors");
  30. const http_1 = require("./http");
  31. const open_1 = require("./open");
  32. class BaseSession {
  33. constructor(e) {
  34. this.e = e;
  35. }
  36. async logout() {
  37. const activeToken = this.e.config.get('tokens.user');
  38. if (activeToken) {
  39. // invalidate the token
  40. const { req } = await this.e.client.make('POST', '/logout');
  41. req.set('Authorization', `Bearer ${activeToken}`)
  42. .send({});
  43. try {
  44. await this.e.client.do(req);
  45. }
  46. catch (e) { }
  47. }
  48. this.e.config.unset('org.id');
  49. this.e.config.unset('user.id');
  50. this.e.config.unset('user.email');
  51. this.e.config.unset('tokens.user');
  52. this.e.config.unset('tokens.refresh');
  53. this.e.config.unset('tokens.expiresInSeconds');
  54. this.e.config.unset('tokens.issuedOn');
  55. this.e.config.unset('tokens.flowName');
  56. this.e.config.set('git.setup', false);
  57. }
  58. isLoggedIn() {
  59. return typeof this.e.config.get('tokens.user') === 'string';
  60. }
  61. getUser() {
  62. const userId = this.e.config.get('user.id');
  63. if (!userId) {
  64. throw new errors_1.SessionException(`Oops, sorry! You'll need to log in:\n ${(0, color_1.input)('ionic login')}\n\n` +
  65. `You can create a new account by signing up:\n\n ${(0, color_1.input)('ionic signup')}\n`);
  66. }
  67. return { id: userId };
  68. }
  69. }
  70. exports.BaseSession = BaseSession;
  71. class ProSession extends BaseSession {
  72. async getUserToken() {
  73. let userToken = this.e.config.get('tokens.user');
  74. if (!userToken) {
  75. throw new errors_1.SessionException(`Oops, sorry! You'll need to log in:\n ${(0, color_1.input)('ionic login')}\n\n` +
  76. `You can create a new account by signing up:\n\n ${(0, color_1.input)('ionic signup')}\n`);
  77. }
  78. const tokenIssuedOn = this.e.config.get('tokens.issuedOn');
  79. const tokenExpirationSeconds = this.e.config.get('tokens.expiresInSeconds');
  80. const refreshToken = this.e.config.get('tokens.refresh');
  81. const flowName = this.e.config.get('tokens.flowName');
  82. // if there is the possibility to refresh the token, try to do it
  83. if (tokenIssuedOn && tokenExpirationSeconds && refreshToken && flowName) {
  84. if (!this.isTokenValid(tokenIssuedOn, tokenExpirationSeconds)) {
  85. userToken = await this.refreshLogin(refreshToken, flowName);
  86. }
  87. }
  88. // otherwise simply return the token
  89. return userToken;
  90. }
  91. isTokenValid(tokenIssuedOn, tokenExpirationSeconds) {
  92. const tokenExpirationMilliSeconds = tokenExpirationSeconds * 1000;
  93. // 15 minutes in milliseconds of margin
  94. const marginExpiration = 15 * 60 * 1000;
  95. const tokenValid = new Date() < new Date(new Date(tokenIssuedOn).getTime() + tokenExpirationMilliSeconds - marginExpiration);
  96. return tokenValid;
  97. }
  98. async login(email, password) {
  99. const { req } = await this.e.client.make('POST', '/login');
  100. req.send({ email, password, source: 'cli' });
  101. try {
  102. const res = await this.e.client.do(req);
  103. if (!(0, guards_1.isLoginResponse)(res)) {
  104. const data = res.data;
  105. if (hasTokenAttribute(data)) {
  106. data.token = '*****';
  107. }
  108. throw new errors_1.FatalException('API request was successful, but the response format was unrecognized.\n' +
  109. (0, http_1.formatResponseError)(req, res.meta.status, data));
  110. }
  111. const { token, user } = res.data;
  112. if (this.e.config.get('user.id') !== user.id) { // User changed
  113. await this.logout();
  114. }
  115. this.e.config.set('user.id', user.id);
  116. this.e.config.set('user.email', email);
  117. this.e.config.set('tokens.user', token);
  118. }
  119. catch (e) {
  120. if ((0, guards_1.isSuperAgentError)(e) && (e.response.status === 401 || e.response.status === 403)) {
  121. throw new errors_1.SessionException('Incorrect email or password.');
  122. }
  123. throw e;
  124. }
  125. }
  126. async ssoLogin(email) {
  127. await this.webLogin();
  128. }
  129. async tokenLogin(token) {
  130. const { UserClient } = await Promise.resolve().then(() => __importStar(require('./user')));
  131. const userClient = new UserClient(token, this.e);
  132. try {
  133. const user = await userClient.loadSelf();
  134. const user_id = user.id;
  135. if (this.e.config.get('user.id') !== user_id) { // User changed
  136. await this.logout();
  137. }
  138. this.e.config.set('user.id', user_id);
  139. this.e.config.set('user.email', user.email);
  140. this.e.config.set('tokens.user', token);
  141. }
  142. catch (e) {
  143. if ((0, guards_1.isSuperAgentError)(e) && (e.response.status === 401 || e.response.status === 403)) {
  144. throw new errors_1.SessionException('Invalid auth token.');
  145. }
  146. throw e;
  147. }
  148. }
  149. async wizardLogin() {
  150. const { OpenIDFlow } = await Promise.resolve().then(() => __importStar(require('./oauth/openid')));
  151. const wizardUrl = new URL(this.e.config.getOpenIDOAuthConfig().authorizationUrl);
  152. wizardUrl.pathname = 'start';
  153. const flow = new OpenIDFlow({}, this.e, wizardUrl.href);
  154. const token = await flow.run();
  155. await this.tokenLogin(token.access_token);
  156. this.e.config.set('tokens.refresh', token.refresh_token);
  157. this.e.config.set('tokens.expiresInSeconds', token.expires_in);
  158. this.e.config.set('tokens.issuedOn', (new Date()).toJSON());
  159. this.e.config.set('tokens.flowName', flow.flowName);
  160. return token.state;
  161. }
  162. async webLogin() {
  163. const { OpenIDFlow } = await Promise.resolve().then(() => __importStar(require('./oauth/openid')));
  164. const flow = new OpenIDFlow({}, this.e);
  165. const token = await flow.run();
  166. await this.tokenLogin(token.access_token);
  167. this.e.config.set('tokens.refresh', token.refresh_token);
  168. this.e.config.set('tokens.expiresInSeconds', token.expires_in);
  169. this.e.config.set('tokens.issuedOn', (new Date()).toJSON());
  170. this.e.config.set('tokens.flowName', flow.flowName);
  171. }
  172. async refreshLogin(refreshToken, flowName) {
  173. let oauthflow;
  174. // having a generic way to access the right refresh token flow
  175. switch (flowName) {
  176. case 'open_id':
  177. const { OpenIDFlow } = await Promise.resolve().then(() => __importStar(require('./oauth/openid')));
  178. oauthflow = new OpenIDFlow({}, this.e);
  179. break;
  180. default:
  181. oauthflow = undefined;
  182. }
  183. if (!oauthflow) {
  184. throw new errors_1.FatalException('Token cannot be refreshed');
  185. }
  186. const token = await oauthflow.exchangeRefreshToken(refreshToken);
  187. await this.tokenLogin(token.access_token);
  188. this.e.config.set('tokens.expiresInSeconds', token.expires_in);
  189. this.e.config.set('tokens.issuedOn', (new Date()).toJSON());
  190. return token.access_token;
  191. }
  192. }
  193. exports.ProSession = ProSession;
  194. async function promptToLogin(env) {
  195. env.log.nl();
  196. env.log.msg(`Log in to your Ionic account!\n` +
  197. `If you don't have one yet, create yours by running: ${(0, color_1.input)(`ionic signup`)}\n`);
  198. const login = await env.prompt({
  199. type: 'confirm',
  200. name: 'login',
  201. message: 'Open the browser to log in to your Ionic account?',
  202. default: true,
  203. });
  204. if (login) {
  205. await env.session.webLogin();
  206. }
  207. }
  208. exports.promptToLogin = promptToLogin;
  209. async function promptToSignup(env) {
  210. env.log.nl();
  211. env.log.msg(`Join the Ionic Community! 💙\n` +
  212. `Connect with millions of developers on the Ionic Forum and get access to live events, news updates, and more.\n\n`);
  213. const create = await env.prompt({
  214. type: 'confirm',
  215. name: 'create',
  216. message: 'Create free Ionic account?',
  217. default: false,
  218. });
  219. if (create) {
  220. const dashUrl = env.config.getDashUrl();
  221. await (0, open_1.openUrl)(`${dashUrl}/signup?source=cli`);
  222. }
  223. }
  224. exports.promptToSignup = promptToSignup;
  225. function hasTokenAttribute(r) {
  226. return r && typeof r === 'object' && typeof r.token === 'string';
  227. }