-
Notifications
You must be signed in to change notification settings - Fork 162
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Cache interactive login credentials (#222)
* Add simple file token cache * Address pr feedback * Added tests for existing and nonexisting cached login * Cleaned up cached login test for login service * Clean up simpleFileTokenCache, add tests * Add param to simpleFileTokenCache constructor for path * Updated simple file token cache tests * Restore fs mocks at end of each test * Fix getTokens in mock * Fixing broken tests * Address pr feedback * Pushing again * Address more pr feedback
- Loading branch information
Showing
10 changed files
with
278 additions
and
70 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import fs from "fs"; | ||
import mockFs from "mock-fs"; | ||
import { MockFactory } from "../../../test/mockFactory"; | ||
import { SimpleFileTokenCache } from "./simpleFileTokenCache"; | ||
|
||
describe("Simple File Token Cache", () => { | ||
const tokenFilePath = "slsTokenCache.json"; | ||
|
||
let fileContent = { | ||
entries: [], | ||
subscriptions: [], | ||
}; | ||
|
||
afterEach(() => { | ||
mockFs.restore(); | ||
}); | ||
|
||
it("Creates a load file on creation if none", () => { | ||
mockFs(); | ||
|
||
const writeFileSpy = jest.spyOn(fs, "writeFileSync"); | ||
new SimpleFileTokenCache(tokenFilePath); | ||
|
||
const expected = { | ||
entries: [], | ||
subscriptions: [], | ||
}; | ||
|
||
expect(writeFileSpy).toBeCalledWith(tokenFilePath, JSON.stringify(expected)); | ||
writeFileSpy.mockRestore(); | ||
}); | ||
|
||
it("Load file on creation if available", () => { | ||
fileContent.entries = MockFactory.createTestTokenCacheEntries(); | ||
|
||
mockFs({ | ||
"slsTokenCache.json": JSON.stringify(fileContent), | ||
}); | ||
|
||
const readFileSpy = jest.spyOn(fs, "readFileSync"); | ||
const tokenCache = new SimpleFileTokenCache(tokenFilePath); | ||
|
||
expect(readFileSpy).toBeCalled(); | ||
expect(tokenCache.first()).not.toBeNull(); | ||
readFileSpy.mockRestore(); | ||
}) | ||
|
||
it("Saves to file after token is added", () => { | ||
mockFs(); | ||
|
||
const writeFileSpy = jest.spyOn(fs, "writeFileSync"); | ||
const tokenCache = new SimpleFileTokenCache(tokenFilePath); | ||
const testEntries = MockFactory.createTestTokenCacheEntries(); | ||
|
||
tokenCache.add(testEntries); | ||
|
||
const expected = { | ||
entries: testEntries, | ||
subscriptions: [], | ||
}; | ||
|
||
expect(tokenCache.isEmpty()).toBe(false); | ||
expect(writeFileSpy).toBeCalledWith(tokenFilePath, JSON.stringify(expected)); | ||
writeFileSpy.mockRestore(); | ||
}); | ||
|
||
it("Saves to file after subscription is added", () => { | ||
mockFs(); | ||
|
||
const writeFileSpy = jest.spyOn(fs, "writeFileSync"); | ||
const testFileCache = new SimpleFileTokenCache(tokenFilePath); | ||
const testSubs = MockFactory.createTestSubscriptions(); | ||
|
||
testFileCache.addSubs(testSubs); | ||
|
||
const expected = { | ||
entries: [], | ||
subscriptions: testSubs, | ||
}; | ||
|
||
expect(writeFileSpy).toBeCalledWith(tokenFilePath, JSON.stringify(expected)); | ||
writeFileSpy.mockRestore(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import fs from "fs"; | ||
import path from "path"; | ||
import os from "os"; | ||
import * as adal from "adal-node"; | ||
|
||
const CONFIG_DIRECTORY = path.join(os.homedir(), ".azure"); | ||
const DEFAULT_SLS_TOKEN_FILE = path.join(CONFIG_DIRECTORY, "slsTokenCache.json"); | ||
|
||
export class SimpleFileTokenCache implements adal.TokenCache { | ||
private entries: any[] = []; | ||
private subscriptions: any[] = []; | ||
|
||
public constructor(private tokenPath: string = DEFAULT_SLS_TOKEN_FILE) { | ||
this.load(); | ||
} | ||
|
||
public add(entries: any[], callback?: (err?: Error, result?: boolean) => void) { | ||
this.entries.push(...entries); | ||
this.save(); | ||
if (callback) { | ||
callback(); | ||
} | ||
} | ||
|
||
public remove(entries: any[], callback?: (err?: Error, result?: null) => void) { | ||
this.entries = this.entries.filter(e => { | ||
return !Object.keys(entries[0]).every(key => e[key] === entries[0][key]); | ||
}); | ||
this.save(); | ||
if (callback) { | ||
callback(); | ||
} | ||
} | ||
|
||
public find(query: any, callback: (err?: Error, result?: any[]) => void) { | ||
let result = this.entries.filter(e => { | ||
return Object.keys(query).every(key => e[key] === query[key]); | ||
}); | ||
callback(null, result); | ||
return result; | ||
} | ||
|
||
//-------- File toke cache specific methods | ||
|
||
public addSubs(subscriptions: any) { | ||
this.subscriptions.push(...subscriptions); | ||
this.subscriptions = this.subscriptions.reduce((accumulator , current) => { | ||
const x = accumulator .find(item => item.id === current.id); | ||
if (!x) { | ||
return accumulator .concat([current]); | ||
} else { | ||
return accumulator ; | ||
} | ||
}, []); | ||
this.save(); | ||
} | ||
|
||
|
||
public clear(callback: any) { | ||
this.entries = []; | ||
this.subscriptions = []; | ||
this.save(); | ||
callback(); | ||
} | ||
|
||
public isEmpty() { | ||
return this.entries.length === 0; | ||
} | ||
|
||
public first() { | ||
return this.entries[0]; | ||
} | ||
|
||
public listSubscriptions() { | ||
return this.subscriptions; | ||
} | ||
|
||
private load() { | ||
if (fs.existsSync(this.tokenPath)) { | ||
let savedCache = JSON.parse(fs.readFileSync(this.tokenPath).toString()); | ||
this.entries = savedCache.entries; | ||
this.entries.map(t => t.expiresOn = new Date(t.expiresOn)) | ||
this.subscriptions = savedCache.subscriptions; | ||
} else { | ||
this.save(); | ||
} | ||
} | ||
|
||
public save() { | ||
fs.writeFileSync(this.tokenPath, JSON.stringify({ entries: this.entries, subscriptions: this.subscriptions })); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.