feat: better but not perfect misskey auth support
This commit is contained in:
parent
d9aa6e4fae
commit
75407827bc
6 changed files with 92 additions and 35 deletions
|
@ -51,6 +51,10 @@ func (f FediverseApp) MastodonCompatible() bool {
|
|||
return f.InstanceType == "mastodon" || f.InstanceType == "pleroma" || f.InstanceType == "akkoma" || f.InstanceType == "pixelfed"
|
||||
}
|
||||
|
||||
func (f FediverseApp) Misskey() bool {
|
||||
return f.InstanceType == "misskey" || f.InstanceType == "foundkey" || f.InstanceType == "calckey"
|
||||
}
|
||||
|
||||
const ErrNoInstanceApp = errors.Sentinel("instance doesn't have an app")
|
||||
|
||||
func (db *DB) FediverseApp(ctx context.Context, instance string) (fa FediverseApp, err error) {
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
|
@ -29,15 +30,6 @@ func (s *Server) misskeyCallback(w http.ResponseWriter, r *http.Request) error {
|
|||
return server.APIError{Code: server.ErrBadRequest}
|
||||
}
|
||||
|
||||
// if the state can't be validated, return
|
||||
if valid, err := s.validateCSRFState(ctx, decoded.State); !valid {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return server.APIError{Code: server.ErrInvalidState}
|
||||
}
|
||||
|
||||
app, err := s.DB.FediverseApp(ctx, decoded.Instance)
|
||||
if err != nil {
|
||||
log.Errorf("getting app for instance %q: %v", decoded.Instance, err)
|
||||
|
@ -48,21 +40,25 @@ func (s *Server) misskeyCallback(w http.ResponseWriter, r *http.Request) error {
|
|||
}
|
||||
}
|
||||
|
||||
token, err := app.ClientConfig().Exchange(ctx, decoded.Code)
|
||||
if err != nil {
|
||||
log.Errorf("exchanging oauth code: %v", err)
|
||||
userkeyReq := struct {
|
||||
AppSecret string `json:"appSecret"`
|
||||
Token string `json:"token"`
|
||||
}{AppSecret: app.ClientSecret, Token: decoded.Code}
|
||||
|
||||
return server.APIError{Code: server.ErrInvalidOAuthCode}
|
||||
b, err := json.Marshal(userkeyReq)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "marshaling json")
|
||||
}
|
||||
|
||||
fmt.Println(string(b))
|
||||
|
||||
// make me user request
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", "https://"+decoded.Instance+"/api/i", nil)
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", "https://"+decoded.Instance+"/api/auth/session/userkey", bytes.NewReader(b))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating i request")
|
||||
return errors.Wrap(err, "creating userkey request")
|
||||
}
|
||||
req.Header.Set("User-Agent", "pronouns.cc/"+server.Tag)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("Authorization", token.Type()+" "+token.AccessToken)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
|
@ -75,13 +71,20 @@ func (s *Server) misskeyCallback(w http.ResponseWriter, r *http.Request) error {
|
|||
return errors.Wrap(err, "reading i response")
|
||||
}
|
||||
|
||||
var mu partialMisskeyAccount
|
||||
err = json.Unmarshal(jb, &mu)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unmarshaling i response")
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
||||
log.Errorf("POST userkey for instance %q (type %v): %v", app.Instance, app.InstanceType, string(jb))
|
||||
return errors.Wrap(err, "error on misskey's end")
|
||||
}
|
||||
|
||||
u, err := s.DB.FediverseUser(ctx, mu.ID, app.ID)
|
||||
var mu struct {
|
||||
User partialMisskeyAccount `json:"user"`
|
||||
}
|
||||
err = json.Unmarshal(jb, &mu)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unmarshaling userkey response")
|
||||
}
|
||||
|
||||
u, err := s.DB.FediverseUser(ctx, mu.User.ID, app.ID)
|
||||
if err == nil {
|
||||
if u.DeletedAt != nil {
|
||||
// store cancel delete token
|
||||
|
@ -104,7 +107,7 @@ func (s *Server) misskeyCallback(w http.ResponseWriter, r *http.Request) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
err = u.UpdateFromFedi(ctx, s.DB, mu.ID, mu.Username, app.ID)
|
||||
err = u.UpdateFromFedi(ctx, s.DB, mu.User.ID, mu.User.Username, app.ID)
|
||||
if err != nil {
|
||||
log.Errorf("updating user %v with misskey info: %v", u.ID, err)
|
||||
}
|
||||
|
@ -141,7 +144,7 @@ func (s *Server) misskeyCallback(w http.ResponseWriter, r *http.Request) error {
|
|||
|
||||
// no user found, so save a ticket + save their Misskey info in Redis
|
||||
ticket := RandBase64(32)
|
||||
err = s.DB.SetJSON(ctx, "misskey:"+ticket, mu, "EX", "600")
|
||||
err = s.DB.SetJSON(ctx, "misskey:"+ticket, mu.User, "EX", "600")
|
||||
if err != nil {
|
||||
log.Errorf("setting misskey user for ticket %q: %v", ticket, err)
|
||||
return err
|
||||
|
@ -149,7 +152,7 @@ func (s *Server) misskeyCallback(w http.ResponseWriter, r *http.Request) error {
|
|||
|
||||
render.JSON(w, r, fediCallbackResponse{
|
||||
HasAccount: false,
|
||||
Fediverse: mu.Username,
|
||||
Fediverse: mu.User.Username,
|
||||
Ticket: ticket,
|
||||
RequireInvite: s.RequireInvite,
|
||||
})
|
||||
|
@ -360,13 +363,14 @@ func (s *Server) noAppMisskeyURL(ctx context.Context, w http.ResponseWriter, r *
|
|||
return errors.Wrap(err, "creating app")
|
||||
}
|
||||
|
||||
state, err := s.setCSRFState(r.Context())
|
||||
_, url, err := s.misskeyURL(ctx, app)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "setting CSRF state")
|
||||
log.Errorf("generating URL for misskey %q: %v", instance, err)
|
||||
return errors.Wrap(err, "generating URL")
|
||||
}
|
||||
|
||||
render.JSON(w, r, FediResponse{
|
||||
URL: app.ClientConfig().AuthCodeURL(state),
|
||||
URL: url,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
@ -382,3 +386,47 @@ type misskeyApp struct {
|
|||
ID string `json:"id"`
|
||||
Secret string `json:"secret"`
|
||||
}
|
||||
|
||||
func (s *Server) misskeyURL(ctx context.Context, app db.FediverseApp) (token, url string, err error) {
|
||||
genSession := struct {
|
||||
AppSecret string `json:"appSecret"`
|
||||
}{AppSecret: app.ClientSecret}
|
||||
|
||||
b, err := json.Marshal(genSession)
|
||||
if err != nil {
|
||||
return token, url, errors.Wrap(err, "marshaling json")
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", "https://"+app.Instance+"/api/auth/session/generate", bytes.NewReader(b))
|
||||
if err != nil {
|
||||
log.Errorf("creating POST session request for %q: %v", app.Instance, err)
|
||||
return token, url, errors.Wrap(err, "creating POST apps request")
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", "pronouns.cc/"+server.Tag)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
log.Errorf("sending POST session request for %q: %v", app.Instance, err)
|
||||
return token, url, errors.Wrap(err, "sending POST apps request")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
jb, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Errorf("reading response for request: %v", err)
|
||||
return token, url, errors.Wrap(err, "reading response")
|
||||
}
|
||||
|
||||
var genSessionResp struct {
|
||||
Token string `json:"token"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
err = json.Unmarshal(jb, &genSessionResp)
|
||||
if err != nil {
|
||||
return token, url, errors.Wrap(err, "unmarshaling misskey response")
|
||||
}
|
||||
|
||||
return genSessionResp.Token, genSessionResp.URL, nil
|
||||
}
|
||||
|
|
|
@ -30,6 +30,18 @@ func (s *Server) getFediverseURL(w http.ResponseWriter, r *http.Request) error {
|
|||
return s.noAppFediverseURL(ctx, w, r, instance)
|
||||
}
|
||||
|
||||
if app.Misskey() {
|
||||
_, url, err := s.misskeyURL(ctx, app)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "generating misskey URL")
|
||||
}
|
||||
|
||||
render.JSON(w, r, FediResponse{
|
||||
URL: url,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
state, err := s.setCSRFState(r.Context())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "setting CSRF state")
|
||||
|
|
|
@ -69,9 +69,6 @@
|
|||
</ListGroup>
|
||||
<Modal header="Pick an instance" isOpen={modalOpen} toggle={toggleModal}>
|
||||
<ModalBody>
|
||||
<p>
|
||||
<strong>Note:</strong> Misskey (and derivatives) are not supported yet, sorry.
|
||||
</p>
|
||||
<Input placeholder="Instance (e.g. mastodon.social)" bind:value={instance} />
|
||||
{#if error}
|
||||
<div class="mt-2">
|
||||
|
|
|
@ -8,8 +8,7 @@ export const load = (async ({ url, params }) => {
|
|||
method: "POST",
|
||||
body: {
|
||||
instance: params.instance,
|
||||
code: url.searchParams.get("code"),
|
||||
state: url.searchParams.get("state"),
|
||||
code: url.searchParams.get("token"),
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -128,9 +128,6 @@
|
|||
</div>
|
||||
<Modal header="Pick an instance" isOpen={fediLinkModalOpen} toggle={toggleFediLinkModal}>
|
||||
<ModalBody>
|
||||
<p>
|
||||
<strong>Note:</strong> Misskey (and derivatives) are not supported yet, sorry.
|
||||
</p>
|
||||
<Input placeholder="Instance (e.g. mastodon.social)" bind:value={instance} />
|
||||
{#if error}
|
||||
<div class="mt-2">
|
||||
|
|
Loading…
Reference in a new issue