handleDeviceCodeRequest creates a 24 digit random device code and 8 digit user code and returns that to the client. The device code is used to poll for an access token, and the user code is displayed to the user and is used to authorize the device. The device code and user code are stored in the ser
(w http.ResponseWriter, r *http.Request)
| 37 | // to the user and is used to authorize the device. The device code and user code are stored in the |
| 38 | // server's device code store. |
| 39 | func (a *Authenticator) handleDeviceCodeRequest(w http.ResponseWriter, r *http.Request) { |
| 40 | if r.Method != http.MethodPost { |
| 41 | http.Error(w, "expected a POST request", http.StatusBadRequest) |
| 42 | return |
| 43 | } |
| 44 | |
| 45 | body, err := io.ReadAll(r.Body) |
| 46 | if err != nil { |
| 47 | internalServerError(w, fmt.Errorf("failed to read request body: %w", err)) |
| 48 | return |
| 49 | } |
| 50 | bodyStr := string(body) |
| 51 | values, err := url.ParseQuery(bodyStr) |
| 52 | if err != nil { |
| 53 | http.Error(w, err.Error(), http.StatusInternalServerError) |
| 54 | return |
| 55 | } |
| 56 | clientID := values.Get("client_id") |
| 57 | if clientID == "" { |
| 58 | http.Error(w, "client_id is required", http.StatusBadRequest) |
| 59 | return |
| 60 | } |
| 61 | scopes := strings.Split(values.Get("scope"), " ") |
| 62 | if len(scopes) == 0 { |
| 63 | http.Error(w, "scope is required", http.StatusBadRequest) |
| 64 | return |
| 65 | } |
| 66 | if len(scopes) > 1 || scopes[0] != "full_account" { |
| 67 | http.Error(w, "invalid scope", http.StatusBadRequest) |
| 68 | return |
| 69 | } |
| 70 | authCode, err := a.admin.IssueDeviceAuthCode(r.Context(), clientID) |
| 71 | if err != nil { |
| 72 | internalServerError(w, fmt.Errorf("failed to issue auth code: %w", err)) |
| 73 | return |
| 74 | } |
| 75 | |
| 76 | // add a "-" after the 4th character |
| 77 | readableUserCode := authCode.UserCode[:4] + "-" + authCode.UserCode[4:] |
| 78 | |
| 79 | qry := map[string]string{"user_code": readableUserCode} |
| 80 | if values.Get("redirect") != "" { |
| 81 | qry["redirect"] = values.Get("redirect") |
| 82 | } else { |
| 83 | qry["redirect"] = a.admin.URLs.AuthCLISuccessUI() |
| 84 | } |
| 85 | |
| 86 | resp := DeviceCodeResponse{ |
| 87 | DeviceCode: authCode.DeviceCode, |
| 88 | UserCode: readableUserCode, |
| 89 | VerificationURI: a.admin.URLs.AuthVerifyDeviceUI(nil), |
| 90 | VerificationCompleteURI: a.admin.URLs.AuthVerifyDeviceUI(qry), |
| 91 | ExpiresIn: int(admin.DeviceAuthCodeTTL.Seconds()), |
| 92 | PollingInterval: 5, |
| 93 | } |
| 94 | |
| 95 | respBytes, err := json.Marshal(resp) |
| 96 | if err != nil { |
nothing calls this directly
no test coverage detected