Skip to content

Commit d8eb3ca

Browse files
authored
Merge pull request #4 from supabase/feature/auth
feat: auth
2 parents 812bbd6 + 95615f1 commit d8eb3ca

File tree

6 files changed

+148
-2
lines changed

6 files changed

+148
-2
lines changed

.prettierignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.expo
2+
.next
3+
node_modules
4+
package-lock.json
5+
docker*

.prettierrc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"trailingComma": "es5",
3+
"tabWidth": 2,
4+
"semi": false,
5+
"singleQuote": true,
6+
"printWidth": 100
7+
}
8+

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"clean": "rimraf lib",
88
"test": "mocha -r @babel/register -r babel-polyfill test/unit/**/*.js",
99
"test:integration": "mocha -r @babel/register -r babel-polyfill test/integration/**/*.js",
10+
"test:auth": "mocha -r @babel/register -r babel-polyfill test/integration/testAuth.js",
1011
"test:integration:full": "docker-compose up -d && sleep 10 && mocha -r @babel/register -r babel-polyfill test/integration/**/*.js ; docker-compose down --remove-orphans",
1112
"test:prod": "cross-env BABEL_ENV=production npm run test",
1213
"test:watch": "npm test -- --watch",
@@ -43,6 +44,7 @@
4344
"babel-polyfill": "^6.26.0",
4445
"babel-preset-minify": "^0.5.1",
4546
"chai": "^4.1.2",
47+
"chai-as-promised": "^7.1.1",
4648
"cross-env": "^7.0.2",
4749
"jest-websocket-mock": "^2.0.1",
4850
"mocha": "^8.0.1",

src/Auth.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
const superagent = require('superagent')
2+
3+
class Auth {
4+
constructor(authUrl, supabaseKey, options = { autoRefreshToken: true }) {
5+
this.authUrl = authUrl
6+
this.accessToken = null
7+
this.refreshToken = null
8+
this.supabaseKey = supabaseKey
9+
this.currentUser = null
10+
this.autoRefreshToken = options.autoRefreshToken
11+
12+
this.signup = async (email, password) => {
13+
const { body } = await superagent
14+
.post(`${authUrl}/signup`, { email, password })
15+
.set('accept', 'json')
16+
.set('apikey', this.supabaseKey)
17+
18+
return body
19+
}
20+
21+
this.login = async (email, password) => {
22+
23+
const response = await superagent
24+
.post(`${authUrl}/token?grant_type=password`, { email, password })
25+
.set('accept', 'json')
26+
.set('apikey', this.supabaseKey)
27+
28+
if (response.status === 200) {
29+
this.accessToken = response.body['access_token']
30+
this.refreshToken = response.body['refresh_token']
31+
if (this.autoRefreshToken && tokenExirySeconds)
32+
setTimeout(this.refreshToken, tokenExirySeconds - 60)
33+
}
34+
return response
35+
}
36+
37+
this.refreshToken = async () => {
38+
const response = await superagent
39+
.post(`${authUrl}/token?grant_type=refresh_token`, { refresh_token: this.refreshToken })
40+
.set('apikey', this.supabaseKey)
41+
42+
if (response.status === 200) {
43+
this.accessToken = response.body['access_token']
44+
this.refreshToken = response.body['refresh_token']
45+
let tokenExirySeconds = response.body['expires_in']
46+
if (this.autoRefreshToken && tokenExirySeconds)
47+
setTimeout(this.refreshToken, tokenExirySeconds - 60)
48+
}
49+
return response
50+
}
51+
52+
this.logout = async () => {
53+
await superagent
54+
.post(`${authUrl}/logout`)
55+
.set('Authorization', `Bearer ${this.accessToken}`)
56+
.set('apikey', this.supabaseKey)
57+
58+
this.currentUser = null
59+
this.accessToken = null
60+
}
61+
62+
// this.setRefreshTokenExpiry = (refreshTokenExpirySeconds) => {
63+
// let bufferSeconds = 60
64+
// let t = new Date() // current time
65+
// this.refreshTokenExpiry = t.setSeconds(
66+
// t.getSeconds() + (refreshTokenExpirySeconds - bufferSeconds)
67+
// )
68+
// }
69+
70+
this.user = async () => {
71+
if (this.currentUser) return this.currentUser
72+
73+
const response = await superagent
74+
.get(`${authUrl}/user`)
75+
.set('Authorization', `Bearer ${this.accessToken}`)
76+
.set('apikey', this.supabaseKey)
77+
78+
if (response.status === 200) {
79+
this.currentUser = response.body
80+
this.currentUser['access_token'] = this.accessToken
81+
this.currentUser['refresh_token'] = this.refreshToken
82+
}
83+
return this.currentUser
84+
}
85+
}
86+
}
87+
88+
export { Auth }

src/index.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { uuid } from './utils/Helpers'
22
import Realtime from './Realtime'
3+
import { Auth } from './Auth'
34
import { PostgrestClient } from '@supabase/postgrest-js'
45

56
class SupabaseClient {
6-
constructor(supabaseUrl, supabaseKey, options = {}) {
7+
constructor(supabaseUrl, supabaseKey, options = { autoRefreshToken: true }) {
78
this.supabaseUrl = null
89
this.supabaseKey = null
910
this.restUrl = null
1011
this.realtimeUrl = null
12+
this.authUrl = null
1113
this.schema = 'public'
1214
this.subscriptions = {}
1315

@@ -17,6 +19,8 @@ class SupabaseClient {
1719
if (options.schema) this.schema = options.schema
1820

1921
this.authenticate(supabaseUrl, supabaseKey)
22+
23+
this.auth = new Auth(this.authUrl, supabaseKey, { autoRefreshToken: options.autoRefreshToken })
2024
}
2125

2226
/**
@@ -28,6 +32,7 @@ class SupabaseClient {
2832
this.supabaseKey = supabaseKey
2933
this.restUrl = `${supabaseUrl}/rest/v1`
3034
this.realtimeUrl = `${supabaseUrl}/realtime/v1`.replace('http', 'ws')
35+
this.authUrl = `${supabaseUrl}/auth/v1`
3136
}
3237

3338
clear() {
@@ -84,8 +89,12 @@ class SupabaseClient {
8489
}
8590

8691
initClient() {
92+
let headers = { apikey: this.supabaseKey }
93+
94+
if (this.auth.accessToken) headers['Authorization'] = `Bearer ${this.auth.accessToken}`
95+
8796
let rest = new PostgrestClient(this.restUrl, {
88-
headers: { apikey: this.supabaseKey },
97+
headers,
8998
schema: this.schema,
9099
})
91100
let api = rest.from(this.tableName)

test/integration/testAuth.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const chai = require('chai')
2+
const expect = chai.expect
3+
const assert = chai.assert
4+
chai.use(require('chai-as-promised'))
5+
6+
import { createClient } from '../../src'
7+
8+
describe('test signing up and logging in as a new user', () => {
9+
const supabase = createClient(
10+
'https://HPPNcyqrPOIDwqzQHjRl.supabase.net',
11+
'JBkDpEMQw9a9yIeVuFhTt5JEhGjQEY'
12+
)
13+
const randomEmail = `a${Math.random()}@google.com`
14+
15+
it('should register a new user', async () => {
16+
const response = await supabase.auth.signup(randomEmail, '11password')
17+
assert(response.email === randomEmail, 'user could not sign up')
18+
})
19+
20+
it('should log in a user and return an access token', async () => {
21+
const response = await supabase.auth.login(randomEmail, '11password')
22+
assert(response.body.access_token !== undefined, 'user could not log in')
23+
})
24+
25+
it('should return the currently logged in user', async () => {
26+
const user = await supabase.auth.user()
27+
assert(user.email === randomEmail, 'user could not be retrieved')
28+
})
29+
30+
it('should logout and invalidate the previous access_token', async () => {
31+
await supabase.auth.logout()
32+
await expect(supabase.auth.user()).to.be.rejectedWith(Error)
33+
})
34+
})

0 commit comments

Comments
 (0)