mirror of
https://github.com/koel/koel
synced 2024-11-10 06:34:14 +00:00
Basically completed jwt
This commit is contained in:
parent
950772a701
commit
12e4bd473f
29 changed files with 493 additions and 166 deletions
|
@ -6,6 +6,7 @@ use App\Services\Lastfm;
|
|||
use Illuminate\Contracts\Auth\Guard;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Redirector;
|
||||
use Tymon\JWTAuth\JWTAuth;
|
||||
|
||||
class LastfmController extends Controller
|
||||
{
|
||||
|
@ -31,19 +32,27 @@ class LastfmController extends Controller
|
|||
*
|
||||
* @param Redirector $redirector
|
||||
* @param Lastfm $lastfm
|
||||
* @param JWTAuth $auth
|
||||
*
|
||||
* @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function connect(Redirector $redirector, Lastfm $lastfm)
|
||||
public function connect(Redirector $redirector, Lastfm $lastfm, JWTAuth $auth = null)
|
||||
{
|
||||
if (!$lastfm->enabled()) {
|
||||
abort(401, 'Koel is not configured to use with Last.fm yet.');
|
||||
}
|
||||
|
||||
$auth = $auth ?: $this->app['tymon.jwt.auth'];
|
||||
|
||||
// A workaround to make sure Tymon's JWTAuth get the correct token via our custom
|
||||
// "jwt-token" query string instead of the default "token".
|
||||
// This is due to the problem that Last.fm returns the token via "token" as well.
|
||||
$auth->parseToken('', '', 'jwt-token');
|
||||
|
||||
return $redirector->to(
|
||||
'https://www.last.fm/api/auth/?api_key='
|
||||
.$lastfm->getKey()
|
||||
.'&cb='.route('lastfm.callback')
|
||||
.'&cb='.urlencode(route('lastfm.callback').'?jwt-token='.$auth->getToken())
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,10 +2,32 @@
|
|||
|
||||
namespace App\Http\Controllers\API;
|
||||
|
||||
use App\Http\Streamers\PHPStreamer;
|
||||
use App\Http\Streamers\XAccelRedirectStreamer;
|
||||
use App\Http\Streamers\XSendFileStreamer;
|
||||
use App\Models\Song;
|
||||
|
||||
class SongController extends Controller
|
||||
{
|
||||
/**
|
||||
* Play a song.
|
||||
*
|
||||
* @link https://github.com/phanan/koel/wiki#streaming-music
|
||||
*
|
||||
* @param Song $song
|
||||
*/
|
||||
public function play(Song $song)
|
||||
{
|
||||
switch (env('STREAMING_METHOD')) {
|
||||
case 'x-sendfile':
|
||||
return (new XSendFileStreamer($song))->stream();
|
||||
case 'x-accel-redirect':
|
||||
return (new XAccelRedirectStreamer($song))->stream();
|
||||
default:
|
||||
return (new PHPStreamer($song))->stream();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get extra information about a song via Last.fm.
|
||||
*
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\User;
|
||||
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
|
||||
use Illuminate\Foundation\Auth\ThrottlesLogins;
|
||||
use Validator;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Registration & Login Controller
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This controller handles the registration of new users, as well as the
|
||||
| authentication of existing users. By default, this controller uses
|
||||
| a simple trait to add these behaviors. Why don't you explore it?
|
||||
|
|
||||
*/
|
||||
|
||||
protected $redirectPath = '/♫';
|
||||
protected $loginPath = '/login';
|
||||
|
||||
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
|
||||
|
||||
/**
|
||||
* Create a new authentication controller instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('guest', ['except' => 'getLogout']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a validator for an incoming registration request.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return \Illuminate\Contracts\Validation\Validator
|
||||
*/
|
||||
protected function validator(array $data)
|
||||
{
|
||||
return Validator::make($data, [
|
||||
'name' => 'required|max:255',
|
||||
'email' => 'required|email|max:255|unique:users',
|
||||
'password' => 'required|confirmed',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new user instance after a valid registration.
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return User
|
||||
*/
|
||||
protected function create(array $data)
|
||||
{
|
||||
return User::create([
|
||||
'name' => $data['name'],
|
||||
'email' => $data['email'],
|
||||
'password' => bcrypt($data['password']),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Foundation\Auth\ResetsPasswords;
|
||||
|
||||
class PasswordController extends Controller
|
||||
{
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Password Reset Controller
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This controller is responsible for handling password reset requests
|
||||
| and uses a simple trait to include this behavior. You're free to
|
||||
| explore this trait and override any methods you wish to tweak.
|
||||
|
|
||||
*/
|
||||
|
||||
use ResetsPasswords;
|
||||
|
||||
/**
|
||||
* Create a new password controller instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('guest');
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ namespace App\Http;
|
|||
use App\Http\Middleware\Authenticate;
|
||||
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode;
|
||||
use Tymon\JWTAuth\Middleware\GetUserFromToken;
|
||||
use App\Http\Middleware\GetUserFromToken;
|
||||
|
||||
class Kernel extends HttpKernel
|
||||
{
|
||||
|
|
19
app/Http/Middleware/BaseMiddleware.php
Normal file
19
app/Http/Middleware/BaseMiddleware.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\JWTAuth;
|
||||
use Illuminate\Events\Dispatcher;
|
||||
use Illuminate\Routing\ResponseFactory;
|
||||
use Tymon\JWTAuth\Middleware\BaseMiddleware as JWTBaseMiddleware;
|
||||
|
||||
abstract class BaseMiddleware extends JWTBaseMiddleware
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(ResponseFactory $response, Dispatcher $events, JWTAuth $auth)
|
||||
{
|
||||
parent::__construct($response, $events, $auth);
|
||||
}
|
||||
}
|
39
app/Http/Middleware/GetUserFromToken.php
Normal file
39
app/Http/Middleware/GetUserFromToken.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Tymon\JWTAuth\Exceptions\JWTException;
|
||||
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
|
||||
|
||||
class GetUserFromToken extends BaseMiddleware
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, \Closure $next)
|
||||
{
|
||||
if (! $token = $this->auth->setRequest($request)->getToken()) {
|
||||
return $this->respond('tymon.jwt.absent', 'token_not_provided', 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$user = $this->auth->authenticate($token);
|
||||
} catch (TokenExpiredException $e) {
|
||||
return $this->respond('tymon.jwt.expired', 'token_expired', $e->getStatusCode(), [$e]);
|
||||
} catch (JWTException $e) {
|
||||
return $this->respond('tymon.jwt.invalid', 'token_invalid', $e->getStatusCode(), [$e]);
|
||||
}
|
||||
|
||||
if (! $user) {
|
||||
return $this->respond('tymon.jwt.user_not_found', 'user_not_found', 404);
|
||||
}
|
||||
|
||||
$this->events->fire('tymon.jwt.valid', $user);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
29
app/Http/Requests/API/UserLoginRequest.php
Normal file
29
app/Http/Requests/API/UserLoginRequest.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\API;
|
||||
|
||||
class UserLoginRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
'email' => 'required|email',
|
||||
'password' => 'required',
|
||||
];
|
||||
}
|
||||
}
|
|
@ -4,39 +4,41 @@ Route::get('/', function () {
|
|||
return view('index');
|
||||
});
|
||||
|
||||
Route::get('{song}/play', 'PlaybackController@play');
|
||||
Route::group(['prefix' => 'api', 'namespace' => 'API'], function () {
|
||||
|
||||
|
||||
Route::group(['prefix' => 'api', 'middleware' => 'jwt.auth', 'namespace' => 'API'], function () {
|
||||
Route::get('/', function () {
|
||||
// Just acting as a ping service.
|
||||
});
|
||||
|
||||
Route::get('data', 'DataController@index');
|
||||
|
||||
Route::post('settings', 'SettingController@save');
|
||||
|
||||
Route::post('{song}/scrobble/{timestamp}', 'SongController@scrobble')->where([
|
||||
'timestamp' => '\d+',
|
||||
]);
|
||||
Route::get('{song}/info', 'SongController@getInfo');
|
||||
|
||||
Route::post('interaction/play', 'InteractionController@play');
|
||||
Route::post('interaction/like', 'InteractionController@like');
|
||||
Route::post('interaction/batch/like', 'InteractionController@batchLike');
|
||||
Route::post('interaction/batch/unlike', 'InteractionController@batchUnlike');
|
||||
|
||||
Route::resource('playlist', 'PlaylistController', ['only' => ['store', 'update', 'destroy']]);
|
||||
Route::put('playlist/{playlist}/sync', 'PlaylistController@sync')->where(['playlist' => '\d+']);
|
||||
|
||||
Route::resource('user', 'UserController', ['only' => ['store', 'update', 'destroy']]);
|
||||
Route::post('me', 'UserController@login');
|
||||
Route::put('me', 'UserController@updateProfile');
|
||||
|
||||
Route::get('lastfm/connect', 'LastfmController@connect');
|
||||
Route::get('lastfm/callback', [
|
||||
'as' => 'lastfm.callback',
|
||||
'uses' => 'LastfmController@callback',
|
||||
]);
|
||||
Route::delete('lastfm/disconnect', 'LastfmController@disconnect');
|
||||
Route::group(['middleware' => 'jwt.auth'], function () {
|
||||
Route::get('/', function () {
|
||||
// Just acting as a ping service.
|
||||
});
|
||||
|
||||
Route::get('data', 'DataController@index');
|
||||
|
||||
Route::post('settings', 'SettingController@save');
|
||||
|
||||
Route::get('{song}/play', 'SongController@play');
|
||||
Route::post('{song}/scrobble/{timestamp}', 'SongController@scrobble')->where([
|
||||
'timestamp' => '\d+',
|
||||
]);
|
||||
Route::get('{song}/info', 'SongController@getInfo');
|
||||
|
||||
Route::post('interaction/play', 'InteractionController@play');
|
||||
Route::post('interaction/like', 'InteractionController@like');
|
||||
Route::post('interaction/batch/like', 'InteractionController@batchLike');
|
||||
Route::post('interaction/batch/unlike', 'InteractionController@batchUnlike');
|
||||
|
||||
Route::resource('playlist', 'PlaylistController', ['only' => ['store', 'update', 'destroy']]);
|
||||
Route::put('playlist/{playlist}/sync', 'PlaylistController@sync')->where(['playlist' => '\d+']);
|
||||
|
||||
Route::resource('user', 'UserController', ['only' => ['store', 'update', 'destroy']]);
|
||||
Route::put('me', 'UserController@updateProfile');
|
||||
|
||||
Route::get('lastfm/connect', 'LastfmController@connect');
|
||||
Route::get('lastfm/callback', [
|
||||
'as' => 'lastfm.callback',
|
||||
'uses' => 'LastfmController@callback',
|
||||
]);
|
||||
Route::delete('lastfm/disconnect', 'LastfmController@disconnect');
|
||||
});
|
||||
});
|
||||
|
|
29
app/JWTAuth.php
Normal file
29
app/JWTAuth.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Tymon\JWTAuth\JWTAuth as BaseJWTAuth;
|
||||
use Tymon\JWTAuth\Exceptions\JWTException;
|
||||
use Tymon\JWTAuth\JWTManager;
|
||||
use Tymon\JWTAuth\Providers\Auth\AuthInterface;
|
||||
use Tymon\JWTAuth\Providers\User\UserInterface;
|
||||
|
||||
class JWTAuth extends BaseJWTAuth
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(JWTManager $manager, UserInterface $user, AuthInterface $auth, Request $request)
|
||||
{
|
||||
return parent::__construct($manager, $user, $auth, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function parseToken($method = 'bearer', $header = 'authorization', $query = 'jwt-token')
|
||||
{
|
||||
return parent::parseToken($method, $header, $query);
|
||||
}
|
||||
}
|
27
app/Listeners/JWTEventListener.php
Normal file
27
app/Listeners/JWTEventListener.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
class JWTEventListener
|
||||
{
|
||||
/**
|
||||
* Handle user login events.
|
||||
*/
|
||||
public function onValidUser($event)
|
||||
{
|
||||
auth()->setUser($event->user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the listeners for the subscriber.
|
||||
*
|
||||
* @param Illuminate\Events\Dispatcher $events
|
||||
*/
|
||||
public function subscribe($events)
|
||||
{
|
||||
$events->listen(
|
||||
'tymon.jwt.valid',
|
||||
'App\Listeners\JWTEventListener@onValidUser'
|
||||
);
|
||||
}
|
||||
}
|
168
config/jwt.php
Normal file
168
config/jwt.php
Normal file
|
@ -0,0 +1,168 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| JWT Authentication Secret
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Don't forget to set this, as it will be used to sign your tokens.
|
||||
| A helper command is provided for this: `php artisan jwt:generate`
|
||||
|
|
||||
*/
|
||||
|
||||
'secret' => env('JWT_SECRET', 'YTAOH41t3QYSCVYuJfGKzSSJ6l1sBtvG'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| JWT time to live
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Specify the length of time (in minutes) that the token will be valid for.
|
||||
| Defaults to 1 hour
|
||||
|
|
||||
*/
|
||||
|
||||
'ttl' => 60 * 24 * 7,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Refresh time to live
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Specify the length of time (in minutes) that the token can be refreshed
|
||||
| within. I.E. The user can refresh their token within a 2 week window of
|
||||
| the original token being created until they must re-authenticate.
|
||||
| Defaults to 2 weeks
|
||||
|
|
||||
*/
|
||||
|
||||
'refresh_ttl' => 20160,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| JWT hashing algorithm
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Specify the hashing algorithm that will be used to sign the token.
|
||||
|
|
||||
| See here: https://github.com/namshi/jose/tree/2.2.0/src/Namshi/JOSE/Signer
|
||||
| for possible values
|
||||
|
|
||||
*/
|
||||
|
||||
'algo' => 'HS256',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| User Model namespace
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Specify the full namespace to your User model.
|
||||
| e.g. 'Acme\Entities\User'
|
||||
|
|
||||
*/
|
||||
|
||||
'user' => App\Models\User::class,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| User identifier
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Specify a unique property of the user that will be added as the 'sub'
|
||||
| claim of the token payload.
|
||||
|
|
||||
*/
|
||||
|
||||
'identifier' => 'id',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Required Claims
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Specify the required claims that must exist in any token.
|
||||
| A TokenInvalidException will be thrown if any of these claims are not
|
||||
| present in the payload.
|
||||
|
|
||||
*/
|
||||
|
||||
'required_claims' => ['iss', 'iat', 'exp', 'nbf', 'sub', 'jti'],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Blacklist Enabled
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| In order to invalidate tokens, you must have the the blacklist enabled.
|
||||
| If you do not want or need this functionality, then set this to false.
|
||||
|
|
||||
*/
|
||||
|
||||
'blacklist_enabled' => env('JWT_BLACKLIST_ENABLED', true),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Providers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Specify the various providers used throughout the package.
|
||||
|
|
||||
*/
|
||||
|
||||
'providers' => [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| User Provider
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Specify the provider that is used to find the user based
|
||||
| on the subject claim
|
||||
|
|
||||
*/
|
||||
|
||||
'user' => Tymon\JWTAuth\Providers\User\EloquentUserAdapter::class,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| JWT Provider
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Specify the provider that is used to create and decode the tokens.
|
||||
|
|
||||
*/
|
||||
|
||||
'jwt' => Tymon\JWTAuth\Providers\JWT\NamshiAdapter::class,
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Authentication Provider
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Specify the provider that is used to authenticate users.
|
||||
|
|
||||
*/
|
||||
|
||||
'auth' => function ($app) {
|
||||
return new Tymon\JWTAuth\Providers\Auth\IlluminateAuthAdapter($app['auth']);
|
||||
},
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Storage Provider
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Specify the provider that is used to store tokens in the blacklist
|
||||
|
|
||||
*/
|
||||
|
||||
'storage' => function ($app) {
|
||||
return new Tymon\JWTAuth\Providers\Storage\IlluminateCacheAdapter($app['cache']);
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
];
|
|
@ -29,6 +29,7 @@
|
|||
import loginForm from './components/auth/login-form.vue';
|
||||
|
||||
import sharedStore from './stores/shared';
|
||||
import queueStore from './stores/queue';
|
||||
import preferenceStore from './stores/preference';
|
||||
import playback from './services/playback';
|
||||
import ls from './services/ls';
|
||||
|
@ -203,6 +204,18 @@
|
|||
setOverlayDimissable() {
|
||||
this.overlayState.dismissable = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Log the current user out and reset the application state.
|
||||
*/
|
||||
logout() {
|
||||
ls.remove('jwt-token');
|
||||
this.authenticated = false;
|
||||
playback.stop();
|
||||
queueStore.clear();
|
||||
this.loadMainView('queue');
|
||||
this.$broadcast('koel:teardown');
|
||||
},
|
||||
},
|
||||
|
||||
events: {
|
||||
|
|
|
@ -48,6 +48,19 @@
|
|||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Reset all applicable child components' states
|
||||
*/
|
||||
resetChildrenStates() {
|
||||
_.each(this.$refs, child => {
|
||||
if (typeof child.resetState === 'function') {
|
||||
child.resetState();
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
events: {
|
||||
'main-content-view:load': function (view) {
|
||||
// Hide the panel away if a main view is triggered on mobile.
|
||||
|
@ -57,17 +70,17 @@
|
|||
},
|
||||
|
||||
'song:play': function (song) {
|
||||
// Reset all applicable child components' states
|
||||
_.each(this.$refs, child => {
|
||||
if (typeof child.resetState === 'function') {
|
||||
child.resetState();
|
||||
}
|
||||
});
|
||||
this.resetChildrenStates();
|
||||
|
||||
songStore.getInfo(song, () => {
|
||||
this.$broadcast('song:info-loaded', song);
|
||||
});
|
||||
},
|
||||
|
||||
'koel:teardown': function () {
|
||||
this.currentView = 'lyrics';
|
||||
this.resetChildrenStates();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -76,6 +76,11 @@
|
|||
'koel:ready': function () {
|
||||
this.displayMore();
|
||||
},
|
||||
|
||||
'koel:teardown': function () {
|
||||
this.q = '';
|
||||
this.numOfItems = 9;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -76,6 +76,11 @@
|
|||
'koel:ready': function () {
|
||||
this.displayMore();
|
||||
},
|
||||
|
||||
'koel:teardown': function () {
|
||||
this.q = '';
|
||||
this.numOfItems = 9;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -104,6 +104,7 @@
|
|||
import preferenceStore from '../../../stores/preference';
|
||||
import sharedStore from '../../../stores/shared';
|
||||
import http from '../../../services/http';
|
||||
import ls from '../../../services/ls';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
@ -156,7 +157,11 @@
|
|||
* Koel will reload once the connection is successful.
|
||||
*/
|
||||
connectToLastfm() {
|
||||
window.open('/api/lastfm/connect', '_blank', 'toolbar=no,titlebar=no,location=no,width=1024,height=640');
|
||||
window.open(
|
||||
`/api/lastfm/connect?jwt-token=${ls.get('jwt-token')}`,
|
||||
'_blank',
|
||||
'toolbar=no,titlebar=no,location=no,width=1024,height=640'
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -265,6 +265,12 @@
|
|||
'main-content-view:load': function (view) {
|
||||
this.viewingQueue = view === 'queue';
|
||||
},
|
||||
|
||||
'koel:teardown': function () {
|
||||
this.song = songStore.stub;
|
||||
this.playing = false;
|
||||
this.liked = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -38,6 +38,10 @@
|
|||
'search:toggle': function () {
|
||||
this.showing = !this.showing;
|
||||
},
|
||||
|
||||
'koel:teardown': function () {
|
||||
this.q = '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<span class="name">{{ state.current.name }}</span>
|
||||
</span>
|
||||
|
||||
<a href="/logout" class="logout"><i class="fa fa-sign-out control"></i></a>
|
||||
<a class="logout" @click.prevent="logout"><i class="fa fa-sign-out control"></i></a>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
|
@ -26,6 +26,10 @@
|
|||
loadProfileView() {
|
||||
this.$root.loadMainView('profile');
|
||||
},
|
||||
|
||||
logout() {
|
||||
this.$root.logout();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -3,6 +3,8 @@ import $ from 'jquery';
|
|||
import ls from './services/ls';
|
||||
|
||||
window.Vue = require('vue');
|
||||
var app = new Vue(require('./app.vue'));
|
||||
|
||||
Vue.config.debug = false;
|
||||
Vue.use(require('vue-resource'));
|
||||
Vue.http.options.root = '/api';
|
||||
|
@ -11,15 +13,15 @@ Vue.http.interceptors.push({
|
|||
var token = ls.get('jwt-token');
|
||||
|
||||
if (token) {
|
||||
Vue.http.headers.common.Authorization = token;
|
||||
Vue.http.headers.common.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
return request;
|
||||
},
|
||||
|
||||
response(response) {
|
||||
if (response.status && response.status.code == 401) {
|
||||
ls.remove('jwt-token');
|
||||
if (response.status === 400 || response.status === 401) {
|
||||
app.logout();
|
||||
}
|
||||
|
||||
if (response.headers && response.headers.Authorization) {
|
||||
|
@ -27,7 +29,7 @@ Vue.http.interceptors.push({
|
|||
}
|
||||
|
||||
if (response.data && response.data.token && response.data.token.length > 10) {
|
||||
ls.set('jwt-token', `Bearer ${response.data.token}`);
|
||||
ls.set('jwt-token', response.data.token);
|
||||
}
|
||||
|
||||
return response;
|
||||
|
@ -38,4 +40,4 @@ Vue.http.interceptors.push({
|
|||
// Enter night,
|
||||
// Take my hand,
|
||||
// We're off to never never land.
|
||||
new Vue(require('./app.vue')).$mount('body');
|
||||
app.$mount('body');
|
||||
|
|
|
@ -34,4 +34,10 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
|
||||
events: {
|
||||
'koel:teardown': function () {
|
||||
this.numOfItems = 30;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -16,9 +16,9 @@ export default {
|
|||
case 'post':
|
||||
return Vue.http.post(url, data, options).then(successCb, errorCb);
|
||||
case 'put':
|
||||
return Vue.http.put(url, data, cb, options).then(successCb, errorCb);
|
||||
return Vue.http.put(url, data, options).then(successCb, errorCb);
|
||||
case 'delete':
|
||||
return Vue.http.delete(url, data, cb, options).then(successCb, errorCb);
|
||||
return Vue.http.delete(url, data, options).then(successCb, errorCb);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -32,10 +32,6 @@ export default {
|
|||
return this.request('post', url, data, successCb, errorCb, options);
|
||||
},
|
||||
|
||||
patch(url, data, successCb = null, errorCb = null, options = {}) {
|
||||
return this.request('patch', url, data, successCb, errorCb, options);
|
||||
},
|
||||
|
||||
put(url, data, successCb = null, errorCb = null, options = {}) {
|
||||
return this.request('put', url, data, successCb, errorCb, options);
|
||||
},
|
||||
|
|
|
@ -6,6 +6,7 @@ import songStore from '../stores/song';
|
|||
import artistStore from '../stores/artist';
|
||||
import albumStore from '../stores/album';
|
||||
import preferenceStore from '../stores/preference';
|
||||
import ls from '../services/ls';
|
||||
import config from '../config';
|
||||
|
||||
export default {
|
||||
|
@ -90,7 +91,7 @@ export default {
|
|||
this.app.$broadcast('song:play', song);
|
||||
|
||||
$('title').text(`${song.title} ♫ Koel`);
|
||||
this.player.source(`/${song.id}/play`);
|
||||
this.player.source(`/api/${song.id}/play?jwt-token=${ls.get('jwt-token')}`);
|
||||
this.player.play();
|
||||
|
||||
// Register the play to the server
|
||||
|
|
|
@ -12,6 +12,10 @@ export default {
|
|||
return this.state.songs;
|
||||
},
|
||||
|
||||
clear() {
|
||||
this.state.songs = [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle like/unlike a song.
|
||||
* A request to the server will be made.
|
||||
|
|
|
@ -13,7 +13,7 @@ export default {
|
|||
// How about another song then?
|
||||
//
|
||||
// LITTLE WING
|
||||
// -- by Jimi Fucking Hendrick
|
||||
// -- by Jimi Fucking Hendrix
|
||||
//
|
||||
// Well she's walking
|
||||
// Through the clouds
|
||||
|
|
|
@ -27,8 +27,10 @@ export default {
|
|||
},
|
||||
|
||||
init(successCb = null, errorCb = null) {
|
||||
http.get('data', {}, response => {
|
||||
assign(this.state, response.data);
|
||||
this.reset();
|
||||
|
||||
http.get('data', data => {
|
||||
assign(this.state, data);
|
||||
|
||||
// If this is a new user, initialize his preferences to be an empty object.
|
||||
if (!this.state.currentUser.preferences) {
|
||||
|
@ -43,15 +45,23 @@ export default {
|
|||
queueStore.init();
|
||||
settingStore.init(this.state.settings);
|
||||
|
||||
window.useLastfm = this.state.useLastfm = response.data.useLastfm;
|
||||
window.useLastfm = this.state.useLastfm = data.useLastfm;
|
||||
}, successCb, errorCb);
|
||||
},
|
||||
|
||||
if (successCb) {
|
||||
successCb();
|
||||
}
|
||||
}, error => {
|
||||
if (errorCb) {
|
||||
errorCb();
|
||||
}
|
||||
});
|
||||
reset() {
|
||||
this.state.songs = [];
|
||||
this.state.albums = [];
|
||||
this.state.artists = [];
|
||||
this.state.favorites = [];
|
||||
this.state.queued = [];
|
||||
this.state.interactions = [];
|
||||
this.state.users = [];
|
||||
this.state.settings = [];
|
||||
this.state.currentUser = null;
|
||||
this.state.playlists = [];
|
||||
this.state.useLastfm = false;
|
||||
this.state.currentVersion = '';
|
||||
this.state.latestVersion = '';
|
||||
},
|
||||
};
|
||||
|
|
|
@ -40,6 +40,8 @@ export default {
|
|||
* @param {Array} interactions The array of interactions of the current user
|
||||
*/
|
||||
initInteractions(interactions) {
|
||||
favoriteStore.clear();
|
||||
|
||||
_.each(interactions, interaction => {
|
||||
var song = this.byId(interaction.song_id);
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ use Illuminate\Foundation\Testing\WithoutMiddleware;
|
|||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Redirector;
|
||||
use Mockery as m;
|
||||
use Tymon\JWTAuth\JWTAuth;
|
||||
|
||||
class LastfmTest extends TestCase
|
||||
{
|
||||
|
@ -134,8 +135,12 @@ class LastfmTest extends TestCase
|
|||
$redirector->shouldReceive('to')->once();
|
||||
|
||||
$guard = m::mock(Guard::class, ['user' => factory(User::class)->create()]);
|
||||
$auth = m::mock(JWTAuth::class, [
|
||||
'parseToken' => '',
|
||||
'getToken' => '',
|
||||
]);
|
||||
|
||||
(new LastfmController($guard))->connect($redirector, new Lastfm());
|
||||
(new LastfmController($guard))->connect($redirector, new Lastfm(), $auth);
|
||||
}
|
||||
|
||||
public function testControllerCallback()
|
||||
|
|
Loading…
Reference in a new issue