~cytrogen/masto-fe

ref: 5c79cd6cf732c348b3cf63e9e6c79d189e42d08d masto-fe/public/auth.js -rw-r--r-- 4.2 KiB
5c79cd6c — Cytrogen [chore] Add .gstack/ to gitignore 4 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
document.addEventListener("DOMContentLoaded", async function() {
  await ready();
  
  const form = document.querySelector('#login')
  form?.addEventListener("submit", (event) => {
    event.preventDefault();
    auth();
  });
});

async function ready() {
  const domain = localStorage.getItem('domain');
  let accessToken = localStorage.getItem(`access_token`);

  if (domain) document.getElementById('instance').value = domain;

  const urlParams = new URLSearchParams(window.location.search);
  const code = urlParams.get('code');

  if (domain && code && !accessToken) await getToken(code, domain).then(res => accessToken = res);
  if (accessToken) {
    window.location.href = '/prepare.html';
  }
}

async function auth() {
  setMessage('Please wait');

  const instance = document.getElementById('instance').value.trim();
  const matches = instance.match(/((?:http|https):\/\/)?(.*)/);

  const protocol = matches[1];
  if (protocol) {
    localStorage.setItem('protocol', protocol);
  }
  
  const domain = matches[2];
  if (!domain) {
    setMessage('Invalid instance', true);
    await new Promise(r => setTimeout(r, 2000));
    setMessage('Authorize', false, false);
    return;
  }
  localStorage.setItem('domain', domain);

  // We need to run this every time in cases like Iceshrimp,
  // where the client id/secret aren't reusable (yet) because
  // they contain use-once session information.
  await registerApp(domain);

  authorize(domain);
}

async function registerApp(domain) {
  setMessage('Registering app');

  const protocol = localStorage.getItem(`protocol`) ?? `https://`;
  const appsUrl = `${protocol}${domain}/api/v1/apps`;
  const formData = new FormData();
  formData.append('client_name', 'Masto-FE (🦥 flavour)');
  formData.append('website', 'https://codeberg.org/superseriousbusiness/masto-fe-standalone');
  formData.append('redirect_uris', document.location.origin + document.location.pathname);
  formData.append('scopes', 'read write follow push');

  // eslint-disable-next-line promise/catch-or-return
  await fetch(appsUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams(formData),
  })
    .then(async res => {
      const app = await res.json();
      localStorage.setItem(`client_id`, app.client_id);
      localStorage.setItem(`client_secret`, app.client_secret);
    });
}

function authorize(domain) {
  setMessage('Authorizing');
  const clientId = localStorage.getItem(`client_id`);
  const protocol = localStorage.getItem(`protocol`) ?? `https://`;
  document.location.href = `${protocol}${domain}/oauth/authorize?response_type=code&client_id=${clientId}&redirect_uri=${document.location.origin + document.location.pathname}&scope=read+write+follow+push`;
}

async function getToken(code, domain) {
  setMessage('Getting token');

  const protocol = localStorage.getItem(`protocol`) ?? `https://`;
  const tokenUrl = `${protocol}${domain}/oauth/token`;
  const clientId = localStorage.getItem(`client_id`);
  const clientSecret = localStorage.getItem(`client_secret`);

  const formData = new FormData();
  formData.append('grant_type', 'authorization_code');
  formData.append('code', code);
  formData.append('client_id', clientId);
  formData.append('client_secret', clientSecret);
  formData.append('scope', 'read write follow push');
  formData.append('redirect_uri', document.location.origin + document.location.pathname);

  // eslint-disable-next-line promise/catch-or-return
  return fetch(tokenUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: new URLSearchParams(formData),
  })
    .then(async res => {
      const app = await res.json();
      if (app.access_token) localStorage.setItem(`access_token`, app.access_token);
      return app.access_token;
    });
}

function setMessage(message, error = false, disabled = true) {
  document.getElementById('message').setAttribute('role', 'status');
  document.getElementById('message').textContent = message;
  document.getElementById('btn').disabled = disabled;

  if (!error) return;

  const instance = document.getElementById('instance');
  instance.setAttribute('aria-invalid', true);
  instance.setAttribute('aria-describedby', 'message');
}