No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

handlers.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.registerHandlers = void 0;
  4. const url_1 = require("url");
  5. const operations_1 = require("./operations");
  6. const errors_1 = require("./errors");
  7. const widget_ui_1 = require("./widget_ui");
  8. function registerHandlers(app, getProjectStateByApiKey) {
  9. app.get(`/emulator/action`, (req, res) => {
  10. const { mode, oobCode, continueUrl, apiKey, tenantId } = req.query;
  11. if (!apiKey) {
  12. return res.status(400).json({
  13. authEmulator: {
  14. error: "missing apiKey query parameter",
  15. instructions: `Please modify the URL to specify an apiKey, such as ...&apiKey=YOUR_API_KEY`,
  16. },
  17. });
  18. }
  19. if (!oobCode) {
  20. return res.status(400).json({
  21. authEmulator: {
  22. error: "missing oobCode query parameter",
  23. instructions: `Please modify the URL to specify an oobCode, such as ...&oobCode=YOUR_OOB_CODE`,
  24. },
  25. });
  26. }
  27. const state = getProjectStateByApiKey(apiKey, tenantId);
  28. switch (mode) {
  29. case "recoverEmail": {
  30. const oob = state.validateOobCode(oobCode);
  31. const RETRY_INSTRUCTIONS = "If you're trying to test the reverting email flow, try changing the email again to generate a new link.";
  32. if ((oob === null || oob === void 0 ? void 0 : oob.requestType) !== "RECOVER_EMAIL") {
  33. return res.status(400).json({
  34. authEmulator: {
  35. error: `Requested mode does not match the OOB code provided.`,
  36. instructions: RETRY_INSTRUCTIONS,
  37. },
  38. });
  39. }
  40. try {
  41. const resp = (0, operations_1.setAccountInfoImpl)(state, {
  42. oobCode,
  43. });
  44. const email = resp.email;
  45. return res.status(200).json({
  46. authEmulator: { success: `The email has been successfully reset.`, email },
  47. });
  48. }
  49. catch (e) {
  50. if (e instanceof errors_1.NotImplementedError ||
  51. (e instanceof errors_1.BadRequestError && e.message === "INVALID_OOB_CODE")) {
  52. return res.status(400).json({
  53. authEmulator: {
  54. error: `Your request to revert your email has expired or the link has already been used.`,
  55. instructions: RETRY_INSTRUCTIONS,
  56. },
  57. });
  58. }
  59. else {
  60. throw e;
  61. }
  62. }
  63. }
  64. case "resetPassword": {
  65. const oob = state.validateOobCode(oobCode);
  66. if ((oob === null || oob === void 0 ? void 0 : oob.requestType) !== "PASSWORD_RESET") {
  67. return res.status(400).json({
  68. authEmulator: {
  69. error: `Your request to reset your password has expired or the link has already been used.`,
  70. instructions: `Try resetting your password again.`,
  71. },
  72. });
  73. }
  74. if (!req.query.newPassword) {
  75. return res.status(400).json({
  76. authEmulator: {
  77. error: "missing newPassword query parameter",
  78. instructions: `To reset the password for ${oob.email}, send an HTTP GET request to the following URL.`,
  79. instructions2: "You may use a web browser or any HTTP client, such as curl.",
  80. urlTemplate: `${oob.oobLink}&newPassword=NEW_PASSWORD_HERE`,
  81. },
  82. });
  83. }
  84. else if (req.query.newPassword === "NEW_PASSWORD_HERE") {
  85. return res.status(400).json({
  86. authEmulator: {
  87. error: "newPassword must be something other than 'NEW_PASSWORD_HERE'",
  88. instructions: "The string 'NEW_PASSWORD_HERE' is just a placeholder.",
  89. instructions2: "Please change the URL to specify a new password instead.",
  90. urlTemplate: `${oob.oobLink}&newPassword=NEW_PASSWORD_HERE`,
  91. },
  92. });
  93. }
  94. const { email } = (0, operations_1.resetPassword)(state, {
  95. oobCode,
  96. newPassword: req.query.newPassword,
  97. });
  98. if (continueUrl) {
  99. return res.redirect(303, continueUrl);
  100. }
  101. else {
  102. return res.status(200).json({
  103. authEmulator: { success: `The password has been successfully updated.`, email },
  104. });
  105. }
  106. }
  107. case "verifyEmail": {
  108. try {
  109. const { email } = (0, operations_1.setAccountInfoImpl)(state, { oobCode });
  110. if (continueUrl) {
  111. return res.redirect(303, continueUrl);
  112. }
  113. else {
  114. return res.status(200).json({
  115. authEmulator: { success: `The email has been successfully verified.`, email },
  116. });
  117. }
  118. }
  119. catch (e) {
  120. if (e instanceof errors_1.NotImplementedError ||
  121. (e instanceof errors_1.BadRequestError && e.message === "INVALID_OOB_CODE")) {
  122. return res.status(400).json({
  123. authEmulator: {
  124. error: `Your request to verify your email has expired or the link has already been used.`,
  125. instructions: `Try verifying your email again.`,
  126. },
  127. });
  128. }
  129. else {
  130. throw e;
  131. }
  132. }
  133. }
  134. case "signIn": {
  135. if (!continueUrl) {
  136. return res.status(400).json({
  137. authEmulator: {
  138. error: "Missing continueUrl query parameter",
  139. instructions: `To sign in, append &continueUrl=YOUR_APP_URL to the link.`,
  140. },
  141. });
  142. }
  143. const redirectTo = new url_1.URL(continueUrl);
  144. for (const name of Object.keys(req.query)) {
  145. if (name !== "continueUrl") {
  146. const query = req.query[name];
  147. if (typeof query === "string") {
  148. redirectTo.searchParams.set(name, query);
  149. }
  150. }
  151. }
  152. return res.redirect(303, redirectTo.toString());
  153. }
  154. default:
  155. return res.status(400).json({ authEmulator: { error: "Invalid mode" } });
  156. }
  157. });
  158. app.get(`/emulator/auth/handler`, (req, res) => {
  159. res.set("Content-Type", "text/html; charset=utf-8");
  160. const apiKey = req.query.apiKey;
  161. const providerId = req.query.providerId;
  162. const tenantId = req.query.tenantId;
  163. if (!apiKey || !providerId) {
  164. return res.status(400).json({
  165. authEmulator: {
  166. error: "missing apiKey or providerId query parameters",
  167. },
  168. });
  169. }
  170. const state = getProjectStateByApiKey(apiKey, tenantId);
  171. const providerInfos = state.listProviderInfosByProviderId(providerId);
  172. const options = providerInfos
  173. .map((info) => `<li class="js-reuse-account mdc-list-item mdc-ripple-upgraded" tabindex="0" data-id-token="${encodeURIComponent(createFakeClaims(info))}">
  174. <span class="mdc-list-item__ripple"></span>
  175. ${info.photoUrl
  176. ? `
  177. <span class="mdc-list-item__graphic profile-photo" style="background-image: url('${info.photoUrl}')"></span>`
  178. : `
  179. <span class="mdc-list-item__graphic material-icons" aria-hidden=true>person</span>`}
  180. <span class="mdc-list-item__text"><span class="mdc-list-item__primary-text">${info.displayName || "(No display name)"}</span>
  181. <span class="mdc-list-item__secondary-text fallback-secondary-text" id="reuse-email">${info.email || ""}</span>
  182. </li>`)
  183. .join("\n");
  184. res.end(widget_ui_1.WIDGET_UI.replace(widget_ui_1.PROVIDERS_LIST_PLACEHOLDER, options));
  185. });
  186. app.get(`/emulator/auth/iframe`, (req, res) => {
  187. res.set("Content-Type", "text/html; charset=utf-8");
  188. res.end(`<!DOCTYPE html>
  189. <meta charset="utf-8">
  190. <title>Auth Emulator Helper Iframe</title>
  191. <script>
  192. // TODO: Support older browsers where URLSearchParams is not available.
  193. var query = new URLSearchParams(location.search);
  194. var apiKey = query.get('apiKey');
  195. var appName = query.get('appName');
  196. if (!apiKey || !appName) {
  197. alert('Auth Emulator Internal Error: Missing query params apiKey or appName for iframe.');
  198. }
  199. var storageKey = apiKey + ':' + appName;
  200. var parentContainer = null;
  201. window.addEventListener('message', function (e) {
  202. if (typeof e.data === 'object' && e.data.eventType === 'sendAuthEvent') {
  203. if (!e.data.data.storageKey === storageKey) {
  204. return alert('Auth Emulator Internal Error: Received request with mismatching storageKey');
  205. }
  206. var authEvent = e.data.data.authEvent;
  207. if (parentContainer) {
  208. sendAuthEvent(authEvent);
  209. } else {
  210. // Store it first, and initFrameMessaging() below will pick it up.
  211. sessionStorage['firebase:redirectEvent:' + storageKey] =
  212. JSON.stringify(authEvent);
  213. }
  214. }
  215. });
  216. function initFrameMessaging() {
  217. parentContainer = gapi.iframes.getContext().getParentIframe();
  218. parentContainer.register('webStorageSupport', function() {
  219. // We must reply to this event, or the JS SDK will not continue with the
  220. // popup flow. Web storage support is not actually needed though.
  221. return { status: 'ACK', webStorageSupport: true };
  222. }, gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER);
  223. var authEvent = null;
  224. var storedEvent = sessionStorage['firebase:redirectEvent:' + storageKey];
  225. if (storedEvent) {
  226. try {
  227. authEvent = JSON.parse(storedEvent);
  228. } catch (_) {
  229. return alert('Auth Emulator Internal Error: Invalid stored event.');
  230. }
  231. }
  232. sendAuthEvent(authEvent);
  233. delete sessionStorage['firebase:redirectEvent:' + storageKey];
  234. }
  235. function sendAuthEvent(authEvent) {
  236. parentContainer.send('authEvent', {
  237. type: 'authEvent',
  238. authEvent: authEvent || { type: 'unknown', error: { code: 'auth/no-auth-event' } },
  239. }, function(responses) {
  240. if (!responses || !responses.length ||
  241. responses[responses.length - 1].status !== 'ACK') {
  242. return alert("Auth Emulator Internal Error: Sending authEvent failed.");
  243. }
  244. }, gapi.iframes.CROSS_ORIGIN_IFRAMES_FILTER);
  245. }
  246. window.gapi_onload = function () {
  247. gapi.load('gapi.iframes', {
  248. callback: initFrameMessaging,
  249. timeout: 10000,
  250. ontimeout: function () {
  251. return alert("Auth Emulator Internal Error: Error loading gapi.iframe! Please check your Internet connection.");
  252. },
  253. });
  254. }
  255. </script>
  256. <script src="https://apis.google.com/js/api.js"></script>
  257. `);
  258. });
  259. }
  260. exports.registerHandlers = registerHandlers;
  261. function createFakeClaims(info) {
  262. const claims = {
  263. sub: info.rawId,
  264. iss: "",
  265. aud: "",
  266. exp: 0,
  267. iat: 0,
  268. name: info.displayName,
  269. screen_name: info.screenName,
  270. email: info.email,
  271. email_verified: true,
  272. picture: info.photoUrl,
  273. };
  274. return JSON.stringify(claims);
  275. }