Skip to content

Commit

Permalink
feat: refresh tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
dgreif committed Jul 7, 2019
1 parent 4549f57 commit 4e78cb5
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 16 deletions.
13 changes: 4 additions & 9 deletions api/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { clientApi, RingRestClient } from './rest-client'
import { clientApi, RingAuth, RingRestClient } from './rest-client'
import { Location } from './location'
import {
ActiveDing,
Expand All @@ -12,19 +12,14 @@ import { RingCamera } from './ring-camera'
import { EMPTY, merge, Subject } from 'rxjs'
import { debounceTime, switchMap, throttleTime } from 'rxjs/operators'

export interface RingAlarmOptions {
email: string
password: string
export type RingAlarmOptions = {
locationIds?: string[]
cameraStatusPollingSeconds?: number
cameraDingsPollingSeconds?: number
}
} & RingAuth

export class RingApi {
public readonly restClient = new RingRestClient(
this.options.email,
this.options.password
)
public readonly restClient = new RingRestClient(this.options)

private locations = this.fetchAndBuildLocations()

Expand Down
57 changes: 50 additions & 7 deletions api/rest-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,34 +50,77 @@ interface Session {
hardwareId: string
}

export interface EmailAuth {
email: string
password: string
}

export interface RefreshTokenAuth {
refreshToken: string
}

export type RingAuth = EmailAuth | RefreshTokenAuth

export class RingRestClient {
// prettier-ignore
private refreshToken = ('refreshToken' in this.authOptions ? this.authOptions.refreshToken : undefined)
private authPromise = this.getAuthToken()
private sessionPromise = this.getSession()

constructor(private email: string, private password: string) {}
constructor(private authOptions: RingAuth) {}

private getGrantData() {
if (this.refreshToken) {
return {
grant_type: 'refresh_token',
refresh_token: this.refreshToken
}
}

const { authOptions } = this
if ('email' in authOptions) {
return {
grant_type: 'password',
password: authOptions.password,
username: authOptions.email
}
}

throw new Error(
'Refresh token is not valid. Unable to authenticate with Ring servers.'
)
}

private async getAuthToken(): Promise<AuthTokenResponse> {
const grantData = this.getGrantData()

private async getAuthToken() {
try {
const response = await requestWithRetry<AuthTokenResponse>({
url: 'https://oauth.ring.com/oauth/token',
data: {
client_id: 'ring_official_android',
grant_type: 'password',
password: this.password,
username: this.email,
scope: 'client'
scope: 'client',
...grantData
},
method: 'POST',
headers: {
'content-type': 'application/json'
}
})

this.refreshToken = response.refresh_token

return response
} catch (requestError) {
if (grantData.refresh_token) {
// failed request with refresh token, try again with username/password
this.refreshToken = undefined
return this.getAuthToken()
}

const errorMessage =
'Failed to fetch oauth token from Ring. Verify that your email and password are correct.'
logError(requestError)
logError(requestError.response)
logError(errorMessage)
throw new Error(errorMessage)
}
Expand Down

0 comments on commit 4e78cb5

Please sign in to comment.